UNPKG

22.6 kBMarkdownView Raw
1# mobx-react
2
3[![CircleCI](https://circleci.com/gh/mobxjs/mobx-react.svg?style=svg)](https://circleci.com/gh/mobxjs/mobx-react)
4[![Join the chat at https://gitter.im/mobxjs/mobx](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mobxjs/mobx?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
5[![CDNJS](https://img.shields.io/cdnjs/v/mobx-react.svg)](https://cdnjs.com/libraries/mobx-react)
6
7Package with React component wrapper for combining React with MobX.
8Exports the `observer` decorator and other utilities.
9For documentation, see the [MobX](https://mobxjs.github.io/mobx) project.
10There is also work-in-progress [user guide](https://mobx-react.js.org) for additional information.
11This package supports both React and React Native.
12
13## Choosing your version
14
15There are currently two actively maintained versions of mobx-react:
16
17| NPM Version | Supported React versions | Supports hook based components |
18| ----------- | ------------------------ | -------------------------------------------------------------------------------- |
19| v6 | 16.8.0 and higher | Yes |
20| v5 | 0.13 and higher | No, but it is possible to use `<Observer>` sections inside hook based components |
21
22The user guide covers this [in a more detail](https://mobx-react.js.org/libraries).
23
24The V5 documentation can be found in the [README_v5](README_v5.md).
25
26Both mobx-react 5 and 6 are compatible with mobx 4 and 5
27
28Version 6 is a repackage of the [mobx-react-lite](https://github.com/mobxjs/mobx-react-lite) package + following features from the `mobx-react@5` package added:
29
30- Support for class based components for `observer` and `@observer`
31- `Provider / inject` to pass stores around (but consider to use `React.createContext` instead)
32- `PropTypes` to describe observable based property checkers (but consider to use TypeScript instead)
33- The `disposeOnUnmount` utility / decorator to easily clean up resources such as reactions created in your class based components.
34
35## Installation
36
37`npm install mobx-react --save`
38
39Or CDN: https://unpkg.com/mobx-react (UMD namespace: `mobxReact`)
40
41```javascript
42import { observer } from "mobx-react"
43```
44
45This package provides the bindings for MobX and React.
46See the [official documentation](http://mobxjs.github.io/mobx/intro/overview.html) for how to get started.
47
48For greenfield projects you might want to consider to use [mobx-react-lite](https://github.com/mobxjs/mobx-react-lite), if you intend to only use function based components. `React.createContext` can be used to pass stores around.
49
50## API documentation
51
52Please check [mobx.js.org](https://mobx.js.org) for the general documentation. The documentation below highlights some specifics.
53
54### `observer(componentClass)`
55
56Function (and decorator) that converts a React component definition, React component class or stand-alone render function into a reactive component, which tracks which observables are used by `render` and automatically re-renders the component when one of these values changes.
57
58When using component classes, `this.props` and `this.state` will be made observables, so the component will react to all changes in props and state that are used by `render`.
59Note that `observer` automatically applies `React.memo` to any functional component you pass to it, however class components should extend `PureComponent` instead of `Component`
60
61See the [MobX](https://mobxjs.github.io/mobx/refguide/observer-component.html) documentation for more details.
62
63```javascript
64import { observer } from "mobx-react"
65
66// ---- ES6 syntax ----
67const TodoView = observer(
68 class TodoView extends React.Component {
69 render() {
70 return <div>{this.props.todo.title}</div>
71 }
72 }
73)
74
75// ---- ESNext syntax with decorator syntax enabled ----
76@observer
77class TodoView extends React.Component {
78 render() {
79 return <div>{this.props.todo.title}</div>
80 }
81}
82
83// ---- or just use function components: ----
84const TodoView = observer(({ todo }) => <div>{todo.title}</div>)
85```
86
87### `Observer`
88
89`Observer` is a React component, which applies `observer` to an anonymous region in your component.
90It takes as children a single, argumentless function which should return exactly one React component.
91The rendering in the function will be tracked and automatically re-rendered when needed.
92This can come in handy when needing to pass render function to external components (for example the React Native listview), or if you
93dislike the `observer` decorator / function.
94
95```javascript
96class App extends React.Component {
97 render() {
98 return (
99 <div>
100 {this.props.person.name}
101 <Observer>{() => <div>{this.props.person.name}</div>}</Observer>
102 </div>
103 )
104 }
105}
106
107const person = observable({ name: "John" })
108
109ReactDOM.render(<App person={person} />, document.body)
110person.name = "Mike" // will cause the Observer region to re-render
111```
112
113In case you are a fan of render props, you can use that instead of children. Be advised, that you cannot use both approaches at once, children have a precedence.
114Example
115
116```javascript
117class App extends React.Component {
118 render() {
119 return (
120 <div>
121 {this.props.person.name}
122 <Observer render={() => <div>{this.props.person.name}</div>} />
123 </div>
124 )
125 }
126}
127
128const person = observable({ name: "John" })
129
130ReactDOM.render(<App person={person} />, document.body)
131person.name = "Mike" // will cause the Observer region to re-render
132```
133
134### `useLocalStore` hook
135
136[User guide](https://mobx-react.js.org/state-local)
137
138Local observable state can be introduced by using the `useLocalStore` hook, that runs once to create an observable store. A quick example would be:
139
140```javascript
141import { useLocalStore, useObserver } from "mobx-react-lite"
142
143const Todo = () => {
144 const todo = useLocalStore(() => ({
145 title: "Test",
146 done: true,
147 toggle() {
148 this.done = !this.done
149 }
150 }))
151
152 return useObserver(() => (
153 <h1 onClick={todo.toggle}>
154 {todo.title} {todo.done ? "[DONE]" : "[TODO]"}
155 </h1>
156 ))
157})
158```
159
160When using `useLocalStore`, all properties of the returned object will be made observable automatically, getters will be turned into computed properties, and methods will be bound to the store and apply mobx transactions automatically. If new class instances are returned from the initializer, they will be kept as is.
161
162It is important to realize that the store is created only once! It is not possible to specify dependencies to force re-creation, _nor should you directly be referring to props for the initializer function_, as changes in those won't propagate.
163
164Instead, if your store needs to refer to props (or `useState` based local state), the `useLocalStore` should be combined with the `useAsObservableSource` hook, see below.
165
166Note that in many cases it is possible to extract the initializer function to a function outside the component definition. Which makes it possible to test the store itself in a more straight-forward manner, and avoids creating the initializer closure on each re-render.
167
168_Note: using `useLocalStore` is mostly beneficial for really complex local state, or to obtain more uniform code base. Note that using a local store might conflict with future React features like concurrent rendering._
169
170### `useAsObservableSource` hook
171
172[User guide](https://mobx-react.js.org/state-outsourcing)
173
174The `useAsObservableSource` hook can be used to turn any set of values into an observable object that has a stable reference (the same object is returned every time from the hook).
175The goal of this hook is to trap React primitives such as props or state (which are not observable themselves) into a local, observable object
176so that the `store` or any reactions created by the component can safely refer to it, and get notified if any of the values change.
177
178The value passed to `useAsObservableSource` should always be an object, and is made only shallowly observable.
179
180The object returned by `useAsObservableSource`, although observable, should be considered read-only for all practical purposes.
181Use `useLocalStore` instead if you want to create local, observable, mutable, state.
182
183Warning: \_the return value of `useAsObservableSource` should never be deconstructed! So, don't write: `const {multiplier} = useAsObservableSource({ multiplier })`!\_useObservable
184
185The following example combines all concepts mentioned so far: `useLocalStore` to create a local store, and `useAsObservableProps` to make the props observable, so that it can be uses savely in `store.multiplied`:
186
187```typescript
188import { observer, useAsObservableSource, useLocalStore } from "mobx-react-lite"
189
190interface CounterProps {
191 multiplier: number
192}
193
194export const Counter = observer(function Counter(props: CounterProps) {
195 const observableProps = useAsObservableSource(props)
196 const store = useLocalStore(() => ({
197 count: 10,
198 get multiplied() {
199 return observableProps.multiplier * this.count
200 },
201 inc() {
202 this.count += 1
203 }
204 }))
205
206 return (
207 <div>
208 Multiplied count: <span>{store.multiplied}</span>
209 <button id="inc" onClick={store.inc}>
210 Increment
211 </button>
212 </div>
213 )
214})
215```
216
217Note that we cannot directly use `props.multiplier` in `multiplied` in the above example, it would not cause the `multiplied` to be invalidated, as it is not observable. Recreating the local store would also not have the desired state, as it would be a shame if it lost its local state such as `count`.
218
219_Performance tip: for optimal performance it is recommend to not use `useAsObservableSource` together on the same component as `observer`, as it might trigger double renderings. In those cases, use `<Observer>` instead._
220
221### Server Side Rendering with `useStaticRendering`
222
223When using server side rendering, normal lifecycle hooks of React components are not fired, as the components are rendered only once.
224Since components are never unmounted, `observer` components would in this case leak memory when being rendered server side.
225To avoid leaking memory, call `useStaticRendering(true)` when using server side rendering.
226
227```javascript
228import { useStaticRendering } from "mobx-react"
229
230useStaticRendering(true)
231```
232
233This makes sure the component won't try to react to any future data changes.
234
235### Which components should be marked with `observer`?
236
237The simple rule of thumb is: _all components that render observable data_.
238If you don't want to mark a component as observer, for example to reduce the dependencies of a generic component package, make sure you only pass it plain data.
239
240### Enabling decorators (optional)
241
242Decorators are currently a stage-2 ESNext feature. How to enable them is documented [here](https://github.com/mobxjs/mobx#enabling-decorators-optional).
243
244### Should I still use smart and dumb components?
245
246See this [thread](https://www.reddit.com/r/reactjs/comments/4vnxg5/free_eggheadio_course_learn_mobx_react_in_30/d61oh0l).
247TL;DR: the conceptual distinction makes a lot of sense when using MobX as well, but use `observer` on all components.
248
249### `PropTypes`
250
251MobX-react provides the following additional `PropTypes` which can be used to validate against MobX structures:
252
253- `observableArray`
254- `observableArrayOf(React.PropTypes.number)`
255- `observableMap`
256- `observableObject`
257- `arrayOrObservableArray`
258- `arrayOrObservableArrayOf(React.PropTypes.number)`
259- `objectOrObservableObject`
260
261Use `import { PropTypes } from "mobx-react"` to import them, then use for example `PropTypes.observableArray`
262
263### `Provider` and `inject`
264
265See also [the migration guide to React Hooks](https://mobx-react.js.org/recipes-migration).
266
267_Note: usually there is no need anymore to use `Provider` / `inject` in new code bases; most of its features are now covered by `React.createContext`._
268
269`Provider` is a component that can pass stores (or other stuff) using React's context mechanism to child components.
270This is useful if you have things that you don't want to pass through multiple layers of components explicitly.
271
272`inject` can be used to pick up those stores. It is a higher order component that takes a list of strings and makes those stores available to the wrapped component.
273
274Example (based on the official [context docs](https://facebook.github.io/react/docs/context.html#passing-info-automatically-through-a-tree)):
275
276```javascript
277@inject("color")
278@observer
279class Button extends React.Component {
280 render() {
281 return <button style={{ background: this.props.color }}>{this.props.children}</button>
282 }
283}
284
285class Message extends React.Component {
286 render() {
287 return (
288 <div>
289 {this.props.text} <Button>Delete</Button>
290 </div>
291 )
292 }
293}
294
295class MessageList extends React.Component {
296 render() {
297 const children = this.props.messages.map(message => <Message text={message.text} />)
298 return (
299 <Provider color="red">
300 <div>{children}</div>
301 </Provider>
302 )
303 }
304}
305```
306
307Notes:
308
309- It is possible to read the stores provided by `Provider` using `React.useContext`, by using the `MobXProviderContext` context that can be imported from `mobx-react`.
310- If a component asks for a store and receives a store via a property with the same name, the property takes precedence. Use this to your advantage when testing!
311- Values provided through `Provider` should be final. Make sure that if you put things in `context` that might change over time, that they are `@observable` or provide some other means to listen to changes, like callbacks. However, if your stores will change over time, like an observable value of another store, MobX will throw an error.
312- When using both `@inject` and `@observer`, make sure to apply them in the correct order: `observer` should be the inner decorator, `inject` the outer. There might be additional decorators in between.
313- The original component wrapped by `inject` is available as the `wrappedComponent` property of the created higher order component.
314
315#### Inject as function
316
317The above example in ES5 would start like:
318
319```javascript
320var Button = inject("color")(
321 observer(
322 class Button extends Component {
323 /* ... etc ... */
324 }
325 )
326)
327```
328
329A functional stateless component would look like:
330
331```javascript
332var Button = inject("color")(
333 observer(({ color }) => {
334 /* ... etc ... */
335 })
336)
337```
338
339#### Customizing inject
340
341Instead of passing a list of store names, it is also possible to create a custom mapper function and pass it to inject.
342The mapper function receives all stores as argument, the properties with which the components are invoked and the context, and should produce a new set of properties,
343that are mapped into the original:
344
345`mapperFunction: (allStores, props, context) => additionalProps`
346
347Since version 4.0 the `mapperFunction` itself is tracked as well, so it is possible to do things like:
348
349```javascript
350const NameDisplayer = ({ name }) => <h1>{name}</h1>
351
352const UserNameDisplayer = inject(stores => ({
353 name: stores.userStore.name
354}))(NameDisplayer)
355
356const user = mobx.observable({
357 name: "Noa"
358})
359
360const App = () => (
361 <Provider userStore={user}>
362 <UserNameDisplayer />
363 </Provider>
364)
365
366ReactDOM.render(<App />, document.body)
367```
368
369_N.B. note that in this *specific* case neither `NameDisplayer` nor `UserNameDisplayer` needs to be decorated with `observer`, since the observable dereferencing is done in the mapper function_
370
371#### Using `PropTypes` and `defaultProps` and other static properties in combination with `inject`
372
373Inject wraps a new component around the component you pass into it.
374This means that assigning a static property to the resulting component, will be applied to the HoC, and not to the original component.
375So if you take the following example:
376
377```javascript
378const UserName = inject("userStore")(({ userStore, bold }) => someRendering())
379
380UserName.propTypes = {
381 bold: PropTypes.boolean.isRequired,
382 userStore: PropTypes.object.isRequired // will always fail
383}
384```
385
386The above propTypes are incorrect, `bold` needs to be provided by the caller of the `UserName` component and is checked by React.
387However, `userStore` does not need to be required! Although it is required for the original stateless function component, it is not
388required for the resulting inject component. After all, the whole point of that component is to provide that `userStore` itself.
389
390So if you want to make assertions on the data that is being injected (either stores or data resulting from a mapper function), the propTypes
391should be defined on the _wrapped_ component. Which is available through the static property `wrappedComponent` on the inject component:
392
393```javascript
394const UserName = inject("userStore")(({ userStore, bold }) => someRendering())
395
396UserName.propTypes = {
397 bold: PropTypes.boolean.isRequired // could be defined either here ...
398}
399
400UserName.wrappedComponent.propTypes = {
401 // ... or here
402 userStore: PropTypes.object.isRequired // correct
403}
404```
405
406The same principle applies to `defaultProps` and other static React properties.
407Note that it is not allowed to redefine `contextTypes` on `inject` components (but is possible to define it on `wrappedComponent`)
408
409Finally, mobx-react will automatically move non React related static properties from wrappedComponent to the inject component so that all static fields are
410actually available to the outside world without needing `.wrappedComponent`.
411
412#### Strongly typing inject
413
414##### With TypeScript
415
416`inject` also accepts a function (`(allStores, nextProps, nextContext) => additionalProps`) that can be used to pick all the desired stores from the available stores like this.
417The `additionalProps` will be merged into the original `nextProps` before being provided to the next component.
418
419```typescript
420import { IUserStore } from "myStore"
421
422@inject(allStores => ({
423 userStore: allStores.userStore as IUserStore
424}))
425class MyComponent extends React.Component<{ userStore?: IUserStore; otherProp: number }, {}> {
426 /* etc */
427}
428```
429
430Make sure to mark `userStore` as an optional property. It should not (necessarily) be passed in by parent components at all!
431
432Note: If you have strict null checking enabled, you could muffle the nullable type by using the `!` operator:
433
434```
435public render() {
436 const {a, b} = this.store!
437 // ...
438}
439```
440
441By [migrating to React Hooks](https://mobx-react.js.org/recipes-migration) you can avoid problems with TypeScript.
442
443#### Testing store injection
444
445It is allowed to pass any declared store in directly as a property as well. This makes it easy to set up individual component tests without a provider.
446
447So if you have in your app something like:
448
449```javascript
450<Provider profile={profile}>
451 <Person age={"30"} />
452</Provider>
453```
454
455In your test you can easily test the `Person` component by passing the necessary store as prop directly:
456
457```
458const profile = new Profile()
459const mountedComponent = mount(
460 <Person age={'30'} profile={profile} />
461)
462```
463
464Bear in mind that using shallow rendering won't provide any useful results when testing injected components; only the injector will be rendered.
465To test with shallow rendering, instantiate the `wrappedComponent` instead: `shallow(<Person.wrappedComponent />)`
466
467### disposeOnUnmount(componentInstance, propertyKey | function | function[])
468
469Function (and decorator) that makes sure a function (usually a disposer such as the ones returned by `reaction`, `autorun`, etc.) is automatically executed as part of the componentWillUnmount lifecycle event.
470
471```javascript
472import { disposeOnUnmount } from "mobx-react"
473
474class SomeComponent extends React.Component {
475 // decorator version
476 @disposeOnUnmount
477 someReactionDisposer = reaction(...)
478
479 // decorator version with arrays
480 @disposeOnUnmount
481 someReactionDisposers = [
482 reaction(...),
483 reaction(...)
484 ]
485
486
487 // function version over properties
488 someReactionDisposer = disposeOnUnmount(this, reaction(...))
489
490 // function version inside methods
491 componentDidMount() {
492 // single function
493 disposeOnUnmount(this, reaction(...))
494
495 // or function array
496 disposeOnUnmount(this, [
497 reaction(...),
498 reaction(...)
499 ])
500 }
501}
502```
503
504## DevTools
505
506`mobx-react@6` and higher are no longer compatible with the mobx-react-devtools.
507That is, the MobX react devtools will no longer show render timings or dependency trees of the component.
508The reason is that the standard React devtools are also capable of highlighting re-rendering components.
509And the dependency tree of a component can now be inspected by the standard devtools as well, as shown in the image below:
510
511![hooks.png](hooks.png)
512
513## FAQ
514
515**Should I use `observer` for each component?**
516
517You should use `observer` on every component that displays observable data.
518Even the small ones. `observer` allows components to render independently from their parent and in general this means that
519the more you use `observer`, the better the performance become.
520The overhead of `observer` itself is negligible.
521See also [Do child components need `@observer`?](https://github.com/mobxjs/mobx/issues/101)
522
523**I see React warnings about `forceUpdate` / `setState` from React**
524
525The following warning will appear if you trigger a re-rendering between instantiating and rendering a component:
526
527```
528Warning: forceUpdate(...): Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.`
529```
530
531-- or --
532
533```
534Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
535```
536
537Usually this means that (another) component is trying to modify observables used by this components in their `constructor` or `getInitialState` methods.
538This violates the React Lifecycle, `componentWillMount` should be used instead if state needs to be modified before mounting.