UNPKG

23.4 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(component)`
55
56Function (and decorator) that converts a React component definition, React component class, or stand-alone render function, into a reactive component. A converted component will track which observables are used by its effective `render` and automatically re-render the component when one of these values changes.
57
58#### Functional Components
59
60`React.memo` is automatically applied to functional components provided to `observer`. `observer` does not accept a functional component already wrapped in `React.memo`, or an `observer`, in order to avoid consequences that might arise as a result of wrapping it twice.
61
62#### Class Components
63
64When 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`.
65
66`shouldComponentUpdate` is not supported. As such, it is recommended that class components extend `React.PureComponent`. The `observer` will automatically patch non-pure class components with an internal implementation of `React.PureComponent` if necessary.
67
68See the [MobX](https://mobxjs.github.io/mobx/refguide/observer-component.html) documentation for more details.
69
70```javascript
71import { observer } from "mobx-react"
72
73// ---- ES6 syntax ----
74const TodoView = observer(
75 class TodoView extends React.Component {
76 render() {
77 return <div>{this.props.todo.title}</div>
78 }
79 }
80)
81
82// ---- ESNext syntax with decorator syntax enabled ----
83@observer
84class TodoView extends React.Component {
85 render() {
86 return <div>{this.props.todo.title}</div>
87 }
88}
89
90// ---- or just use function components: ----
91const TodoView = observer(({ todo }) => <div>{todo.title}</div>)
92```
93
94### `Observer`
95
96`Observer` is a React component, which applies `observer` to an anonymous region in your component.
97It takes as children a single, argumentless function which should return exactly one React component.
98The rendering in the function will be tracked and automatically re-rendered when needed.
99This can come in handy when needing to pass render function to external components (for example the React Native listview), or if you
100dislike the `observer` decorator / function.
101
102```javascript
103class App extends React.Component {
104 render() {
105 return (
106 <div>
107 {this.props.person.name}
108 <Observer>{() => <div>{this.props.person.name}</div>}</Observer>
109 </div>
110 )
111 }
112}
113
114const person = observable({ name: "John" })
115
116ReactDOM.render(<App person={person} />, document.body)
117person.name = "Mike" // will cause the Observer region to re-render
118```
119
120In 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.
121Example
122
123```javascript
124class App extends React.Component {
125 render() {
126 return (
127 <div>
128 {this.props.person.name}
129 <Observer render={() => <div>{this.props.person.name}</div>} />
130 </div>
131 )
132 }
133}
134
135const person = observable({ name: "John" })
136
137ReactDOM.render(<App person={person} />, document.body)
138person.name = "Mike" // will cause the Observer region to re-render
139```
140
141### `useLocalStore` hook
142
143[User guide](https://mobx-react.js.org/state-local)
144
145Local observable state can be introduced by using the `useLocalStore` hook, that runs once to create an observable store. A quick example would be:
146
147```javascript
148import { useLocalStore, useObserver } from "mobx-react-lite"
149
150const Todo = () => {
151 const todo = useLocalStore(() => ({
152 title: "Test",
153 done: true,
154 toggle() {
155 this.done = !this.done
156 }
157 }))
158
159 return useObserver(() => (
160 <h1 onClick={todo.toggle}>
161 {todo.title} {todo.done ? "[DONE]" : "[TODO]"}
162 </h1>
163 ))
164}
165```
166
167When 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.
168
169It 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.
170
171Instead, if your store needs to refer to props (or `useState` based local state), the `useLocalStore` should be combined with the `useAsObservableSource` hook, see below.
172
173Note 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.
174
175_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._
176
177### `useAsObservableSource` hook
178
179[User guide](https://mobx-react.js.org/state-outsourcing)
180
181The `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).
182The goal of this hook is to trap React primitives such as props or state (which are not observable themselves) into a local, observable object
183so that the `store` or any reactions created by the component can safely refer to it, and get notified if any of the values change.
184
185The value passed to `useAsObservableSource` should always be an object, and is made only shallowly observable.
186
187The object returned by `useAsObservableSource`, although observable, should be considered read-only for all practical purposes.
188Use `useLocalStore` instead if you want to create local, observable, mutable, state.
189
190Warning: \_the return value of `useAsObservableSource` should never be deconstructed! So, don't write: `const {multiplier} = useAsObservableSource({ multiplier })`!\_useObservable
191
192The 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 safely in `store.multiplied`:
193
194```typescript
195import { observer, useAsObservableSource, useLocalStore } from "mobx-react-lite"
196
197interface CounterProps {
198 multiplier: number
199}
200
201export const Counter = observer(function Counter(props: CounterProps) {
202 const observableProps = useAsObservableSource(props)
203 const store = useLocalStore(() => ({
204 count: 10,
205 get multiplied() {
206 return observableProps.multiplier * this.count
207 },
208 inc() {
209 this.count += 1
210 }
211 }))
212
213 return (
214 <div>
215 Multiplied count: <span>{store.multiplied}</span>
216 <button id="inc" onClick={store.inc}>
217 Increment
218 </button>
219 </div>
220 )
221})
222```
223
224Note 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`.
225
226_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._
227
228### Server Side Rendering with `useStaticRendering`
229
230When using server side rendering, normal lifecycle hooks of React components are not fired, as the components are rendered only once.
231Since components are never unmounted, `observer` components would in this case leak memory when being rendered server side.
232To avoid leaking memory, call `useStaticRendering(true)` when using server side rendering.
233
234```javascript
235import { useStaticRendering } from "mobx-react"
236
237useStaticRendering(true)
238```
239
240This makes sure the component won't try to react to any future data changes.
241
242### Which components should be marked with `observer`?
243
244The simple rule of thumb is: _all components that render observable data_.
245If 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.
246
247### Enabling decorators (optional)
248
249Decorators are currently a stage-2 ESNext feature. How to enable them is documented [here](https://github.com/mobxjs/mobx#enabling-decorators-optional).
250
251### Should I still use smart and dumb components?
252
253See this [thread](https://www.reddit.com/r/reactjs/comments/4vnxg5/free_eggheadio_course_learn_mobx_react_in_30/d61oh0l).
254TL;DR: the conceptual distinction makes a lot of sense when using MobX as well, but use `observer` on all components.
255
256### `PropTypes`
257
258MobX-react provides the following additional `PropTypes` which can be used to validate against MobX structures:
259
260- `observableArray`
261- `observableArrayOf(React.PropTypes.number)`
262- `observableMap`
263- `observableObject`
264- `arrayOrObservableArray`
265- `arrayOrObservableArrayOf(React.PropTypes.number)`
266- `objectOrObservableObject`
267
268Use `import { PropTypes } from "mobx-react"` to import them, then use for example `PropTypes.observableArray`
269
270### `Provider` and `inject`
271
272See also [the migration guide to React Hooks](https://mobx-react.js.org/recipes-migration).
273
274_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`._
275
276`Provider` is a component that can pass stores (or other stuff) using React's context mechanism to child components.
277This is useful if you have things that you don't want to pass through multiple layers of components explicitly.
278
279`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.
280
281Example (based on the official [context docs](https://facebook.github.io/react/docs/context.html#passing-info-automatically-through-a-tree)):
282
283```javascript
284@inject("color")
285@observer
286class Button extends React.Component {
287 render() {
288 return <button style={{ background: this.props.color }}>{this.props.children}</button>
289 }
290}
291
292class Message extends React.Component {
293 render() {
294 return (
295 <div>
296 {this.props.text} <Button>Delete</Button>
297 </div>
298 )
299 }
300}
301
302class MessageList extends React.Component {
303 render() {
304 const children = this.props.messages.map(message => <Message text={message.text} />)
305 return (
306 <Provider color="red">
307 <div>{children}</div>
308 </Provider>
309 )
310 }
311}
312```
313
314Notes:
315
316- 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`.
317- 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!
318- 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.
319- The original component wrapped by `inject` is available as the `wrappedComponent` property of the created higher order component.
320
321#### "The set of provided stores has changed" error
322
323Values 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.
324This restriction exists mainly for legacy reasons. If you have a scenario where you need to modify the set of stores, please leave a comment about it in this issue https://github.com/mobxjs/mobx-react/issues/745. Or a preferred way is to [use React Context](https://mobx-react.js.org/recipes-context) directly which does not have this restriction.
325
326#### Inject as function
327
328The above example in ES5 would start like:
329
330```javascript
331var Button = inject("color")(
332 observer(
333 class Button extends Component {
334 /* ... etc ... */
335 }
336 )
337)
338```
339
340A functional stateless component would look like:
341
342```javascript
343var Button = inject("color")(
344 observer(({ color }) => {
345 /* ... etc ... */
346 })
347)
348```
349
350#### Customizing inject
351
352Instead of passing a list of store names, it is also possible to create a custom mapper function and pass it to inject.
353The 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,
354that are mapped into the original:
355
356`mapperFunction: (allStores, props, context) => additionalProps`
357
358Since version 4.0 the `mapperFunction` itself is tracked as well, so it is possible to do things like:
359
360```javascript
361const NameDisplayer = ({ name }) => <h1>{name}</h1>
362
363const UserNameDisplayer = inject(stores => ({
364 name: stores.userStore.name
365}))(NameDisplayer)
366
367const user = mobx.observable({
368 name: "Noa"
369})
370
371const App = () => (
372 <Provider userStore={user}>
373 <UserNameDisplayer />
374 </Provider>
375)
376
377ReactDOM.render(<App />, document.body)
378```
379
380_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_
381
382#### Using `PropTypes` and `defaultProps` and other static properties in combination with `inject`
383
384Inject wraps a new component around the component you pass into it.
385This means that assigning a static property to the resulting component, will be applied to the HoC, and not to the original component.
386So if you take the following example:
387
388```javascript
389const UserName = inject("userStore")(({ userStore, bold }) => someRendering())
390
391UserName.propTypes = {
392 bold: PropTypes.boolean.isRequired,
393 userStore: PropTypes.object.isRequired // will always fail
394}
395```
396
397The above propTypes are incorrect, `bold` needs to be provided by the caller of the `UserName` component and is checked by React.
398However, `userStore` does not need to be required! Although it is required for the original stateless function component, it is not
399required for the resulting inject component. After all, the whole point of that component is to provide that `userStore` itself.
400
401So if you want to make assertions on the data that is being injected (either stores or data resulting from a mapper function), the propTypes
402should be defined on the _wrapped_ component. Which is available through the static property `wrappedComponent` on the inject component:
403
404```javascript
405const UserName = inject("userStore")(({ userStore, bold }) => someRendering())
406
407UserName.propTypes = {
408 bold: PropTypes.boolean.isRequired // could be defined either here ...
409}
410
411UserName.wrappedComponent.propTypes = {
412 // ... or here
413 userStore: PropTypes.object.isRequired // correct
414}
415```
416
417The same principle applies to `defaultProps` and other static React properties.
418Note that it is not allowed to redefine `contextTypes` on `inject` components (but is possible to define it on `wrappedComponent`)
419
420Finally, mobx-react will automatically move non React related static properties from wrappedComponent to the inject component so that all static fields are
421actually available to the outside world without needing `.wrappedComponent`.
422
423#### Strongly typing inject
424
425##### With TypeScript
426
427`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.
428The `additionalProps` will be merged into the original `nextProps` before being provided to the next component.
429
430```typescript
431import { IUserStore } from "myStore"
432
433@inject(allStores => ({
434 userStore: allStores.userStore as IUserStore
435}))
436class MyComponent extends React.Component<{ userStore?: IUserStore; otherProp: number }, {}> {
437 /* etc */
438}
439```
440
441Make sure to mark `userStore` as an optional property. It should not (necessarily) be passed in by parent components at all!
442
443Note: If you have strict null checking enabled, you could muffle the nullable type by using the `!` operator:
444
445```
446public render() {
447 const {a, b} = this.store!
448 // ...
449}
450```
451
452By [migrating to React Hooks](https://mobx-react.js.org/recipes-migration) you can avoid problems with TypeScript.
453
454#### Testing store injection
455
456It 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.
457
458So if you have in your app something like:
459
460```javascript
461<Provider profile={profile}>
462 <Person age={"30"} />
463</Provider>
464```
465
466In your test you can easily test the `Person` component by passing the necessary store as prop directly:
467
468```
469const profile = new Profile()
470const mountedComponent = mount(
471 <Person age={'30'} profile={profile} />
472)
473```
474
475Bear in mind that using shallow rendering won't provide any useful results when testing injected components; only the injector will be rendered.
476To test with shallow rendering, instantiate the `wrappedComponent` instead: `shallow(<Person.wrappedComponent />)`
477
478### disposeOnUnmount(componentInstance, propertyKey | function | function[])
479
480Function (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.
481
482```javascript
483import { disposeOnUnmount } from "mobx-react"
484
485class SomeComponent extends React.Component {
486 // decorator version
487 @disposeOnUnmount
488 someReactionDisposer = reaction(...)
489
490 // decorator version with arrays
491 @disposeOnUnmount
492 someReactionDisposers = [
493 reaction(...),
494 reaction(...)
495 ]
496
497
498 // function version over properties
499 someReactionDisposer = disposeOnUnmount(this, reaction(...))
500
501 // function version inside methods
502 componentDidMount() {
503 // single function
504 disposeOnUnmount(this, reaction(...))
505
506 // or function array
507 disposeOnUnmount(this, [
508 reaction(...),
509 reaction(...)
510 ])
511 }
512}
513```
514
515## DevTools
516
517`mobx-react@6` and higher are no longer compatible with the mobx-react-devtools.
518That is, the MobX react devtools will no longer show render timings or dependency trees of the component.
519The reason is that the standard React devtools are also capable of highlighting re-rendering components.
520And the dependency tree of a component can now be inspected by the standard devtools as well, as shown in the image below:
521
522![hooks.png](hooks.png)
523
524## FAQ
525
526**Should I use `observer` for each component?**
527
528You should use `observer` on every component that displays observable data.
529Even the small ones. `observer` allows components to render independently from their parent and in general this means that
530the more you use `observer`, the better the performance become.
531The overhead of `observer` itself is negligible.
532See also [Do child components need `@observer`?](https://github.com/mobxjs/mobx/issues/101)
533
534**I see React warnings about `forceUpdate` / `setState` from React**
535
536The following warning will appear if you trigger a re-rendering between instantiating and rendering a component:
537
538```
539
540Warning: forceUpdate(...): Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.`
541
542```
543
544-- or --
545
546```
547
548Warning: 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`.
549
550```
551
552Usually this means that (another) component is trying to modify observables used by this components in their `constructor` or `getInitialState` methods.
553This violates the React Lifecycle, `componentWillMount` should be used instead if state needs to be modified before mounting.