UNPKG

22.9 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- 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.
312- The original component wrapped by `inject` is available as the `wrappedComponent` property of the created higher order component.
313
314#### "The set of provided stores has changed" error
315Values 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.
316This 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.
317
318#### Inject as function
319
320The above example in ES5 would start like:
321
322```javascript
323var Button = inject("color")(
324 observer(
325 class Button extends Component {
326 /* ... etc ... */
327 }
328 )
329)
330```
331
332A functional stateless component would look like:
333
334```javascript
335var Button = inject("color")(
336 observer(({ color }) => {
337 /* ... etc ... */
338 })
339)
340```
341
342#### Customizing inject
343
344Instead of passing a list of store names, it is also possible to create a custom mapper function and pass it to inject.
345The 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,
346that are mapped into the original:
347
348`mapperFunction: (allStores, props, context) => additionalProps`
349
350Since version 4.0 the `mapperFunction` itself is tracked as well, so it is possible to do things like:
351
352```javascript
353const NameDisplayer = ({ name }) => <h1>{name}</h1>
354
355const UserNameDisplayer = inject(stores => ({
356 name: stores.userStore.name
357}))(NameDisplayer)
358
359const user = mobx.observable({
360 name: "Noa"
361})
362
363const App = () => (
364 <Provider userStore={user}>
365 <UserNameDisplayer />
366 </Provider>
367)
368
369ReactDOM.render(<App />, document.body)
370```
371
372_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_
373
374#### Using `PropTypes` and `defaultProps` and other static properties in combination with `inject`
375
376Inject wraps a new component around the component you pass into it.
377This means that assigning a static property to the resulting component, will be applied to the HoC, and not to the original component.
378So if you take the following example:
379
380```javascript
381const UserName = inject("userStore")(({ userStore, bold }) => someRendering())
382
383UserName.propTypes = {
384 bold: PropTypes.boolean.isRequired,
385 userStore: PropTypes.object.isRequired // will always fail
386}
387```
388
389The above propTypes are incorrect, `bold` needs to be provided by the caller of the `UserName` component and is checked by React.
390However, `userStore` does not need to be required! Although it is required for the original stateless function component, it is not
391required for the resulting inject component. After all, the whole point of that component is to provide that `userStore` itself.
392
393So if you want to make assertions on the data that is being injected (either stores or data resulting from a mapper function), the propTypes
394should be defined on the _wrapped_ component. Which is available through the static property `wrappedComponent` on the inject component:
395
396```javascript
397const UserName = inject("userStore")(({ userStore, bold }) => someRendering())
398
399UserName.propTypes = {
400 bold: PropTypes.boolean.isRequired // could be defined either here ...
401}
402
403UserName.wrappedComponent.propTypes = {
404 // ... or here
405 userStore: PropTypes.object.isRequired // correct
406}
407```
408
409The same principle applies to `defaultProps` and other static React properties.
410Note that it is not allowed to redefine `contextTypes` on `inject` components (but is possible to define it on `wrappedComponent`)
411
412Finally, mobx-react will automatically move non React related static properties from wrappedComponent to the inject component so that all static fields are
413actually available to the outside world without needing `.wrappedComponent`.
414
415#### Strongly typing inject
416
417##### With TypeScript
418
419`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.
420The `additionalProps` will be merged into the original `nextProps` before being provided to the next component.
421
422```typescript
423import { IUserStore } from "myStore"
424
425@inject(allStores => ({
426 userStore: allStores.userStore as IUserStore
427}))
428class MyComponent extends React.Component<{ userStore?: IUserStore; otherProp: number }, {}> {
429 /* etc */
430}
431```
432
433Make sure to mark `userStore` as an optional property. It should not (necessarily) be passed in by parent components at all!
434
435Note: If you have strict null checking enabled, you could muffle the nullable type by using the `!` operator:
436
437```
438public render() {
439 const {a, b} = this.store!
440 // ...
441}
442```
443
444By [migrating to React Hooks](https://mobx-react.js.org/recipes-migration) you can avoid problems with TypeScript.
445
446#### Testing store injection
447
448It 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.
449
450So if you have in your app something like:
451
452```javascript
453<Provider profile={profile}>
454 <Person age={"30"} />
455</Provider>
456```
457
458In your test you can easily test the `Person` component by passing the necessary store as prop directly:
459
460```
461const profile = new Profile()
462const mountedComponent = mount(
463 <Person age={'30'} profile={profile} />
464)
465```
466
467Bear in mind that using shallow rendering won't provide any useful results when testing injected components; only the injector will be rendered.
468To test with shallow rendering, instantiate the `wrappedComponent` instead: `shallow(<Person.wrappedComponent />)`
469
470### disposeOnUnmount(componentInstance, propertyKey | function | function[])
471
472Function (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.
473
474```javascript
475import { disposeOnUnmount } from "mobx-react"
476
477class SomeComponent extends React.Component {
478 // decorator version
479 @disposeOnUnmount
480 someReactionDisposer = reaction(...)
481
482 // decorator version with arrays
483 @disposeOnUnmount
484 someReactionDisposers = [
485 reaction(...),
486 reaction(...)
487 ]
488
489
490 // function version over properties
491 someReactionDisposer = disposeOnUnmount(this, reaction(...))
492
493 // function version inside methods
494 componentDidMount() {
495 // single function
496 disposeOnUnmount(this, reaction(...))
497
498 // or function array
499 disposeOnUnmount(this, [
500 reaction(...),
501 reaction(...)
502 ])
503 }
504}
505```
506
507## DevTools
508
509`mobx-react@6` and higher are no longer compatible with the mobx-react-devtools.
510That is, the MobX react devtools will no longer show render timings or dependency trees of the component.
511The reason is that the standard React devtools are also capable of highlighting re-rendering components.
512And the dependency tree of a component can now be inspected by the standard devtools as well, as shown in the image below:
513
514![hooks.png](hooks.png)
515
516## FAQ
517
518**Should I use `observer` for each component?**
519
520You should use `observer` on every component that displays observable data.
521Even the small ones. `observer` allows components to render independently from their parent and in general this means that
522the more you use `observer`, the better the performance become.
523The overhead of `observer` itself is negligible.
524See also [Do child components need `@observer`?](https://github.com/mobxjs/mobx/issues/101)
525
526**I see React warnings about `forceUpdate` / `setState` from React**
527
528The following warning will appear if you trigger a re-rendering between instantiating and rendering a component:
529
530```
531Warning: forceUpdate(...): Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.`
532```
533
534-- or --
535
536```
537Warning: 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`.
538```
539
540Usually this means that (another) component is trying to modify observables used by this components in their `constructor` or `getInitialState` methods.
541This violates the React Lifecycle, `componentWillMount` should be used instead if state needs to be modified before mounting.