UNPKG

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