The observer
function / decorator can be used to turn ReactJS components into reactive components.
It wraps the component's render function in mobx.autorun
to make sure that any data that is used during the rendering of a component forces a re-rendering upon change.
It is available through the separate mobx-react
package.
import {observer} from "mobx-react";
var timerData = observable({
secondsPassed: 0
});
setInterval(() => {
timerData.secondsPassed++;
}, 1000);
@observer class Timer extends React.Component {
render() {
return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
}
});
React.render(<Timer timerData={timerData} />, document.body);
Tip: when observer
needs to be combined with other decorators or higher-order-components, make sure that observer
is the innermost (first applied) decorator;
otherwise it might do nothing at all.
Note that using @observer
as decorator is optional, observer(class Timer ... { })
achieves exactly the same.
MobX can do a lot, but it cannot make primitive values observable (although it can wrap them in an object see boxed observables).
So not the values that are observable, but the properties of an object. This means that @observer
actually reacts to the fact that you dereference a value.
So in our above example, the Timer
component would not react if it was initialized as follows:
React.render(<Timer timerData={timerData.secondsPassed} />, document.body)
In this snippet just the current value of secondsPassed
is passed to the Timer
, which is the immutable value 0
(all primitives are immutable in JS).
That number won't change anymore in the future, so Timer
will never update. It is the property secondsPassed
that will change in the future,
so we need to access it in the component. Or in other words: values need to be passed by reference and not by value.
In ES5 environments, observer components can be simple declared using observer(React.createClass({ ...
. See also the syntax guide
The above timer widget could also be written using stateless function components that are passed through observer
:
import {observer} from "mobx-react";
const Timer = observer(({ timerData }) =>
<span>Seconds passed: { timerData.secondsPassed } </span>
);
Just like normal classes, you can introduce observable properties on a component by using the @observable
decorator.
This means that you can have local state in components that doesn't need to be manged by React's verbose and imperative setState
mechanism, but is as powerful.
The reactive state will be picked up by render
but will not explicitly invoke other React lifecycle methods like componentShouldUpdate
or componentWillUpdate
.
If you need those, just use the normal React state
based APIs.
The example above could also have been written as:
import {observer} from "mobx-react"
import {observable} from "mobx"
@observer class Timer extends React.Component {
@observable secondsPassed = 0
componentWillMount() {
setInterval(() => {
this.secondsPassed++
}, 1000)
}
render() {
return (<span>Seconds passed: { this.secondsPassed } </span> )
}
})
React.render(<Timer />, document.body)
For more advantages of using observable local component state, see 3 reasons why I stopped using setState
.
The mobx-react
package also provides the Provider
component that can be used to pass down stores using React's context mechanism.
To connect to those stores, pass an array of store names to observer
, which will make the stores available as props.
This is supported when using the decorator (@observer(["store"]) class ...
, or the function `observer(["store"], React.createClass({ ...``.
Example:
const colors = observable({
foreground: '#000',
background: '#fff'
});
const App = () =>
<Provider colors={colors}>
<app stuff... />
</Provider>;
const Button = observer(["colors"], ({ colors, label, onClick }) =>
<button style={{
color: colors.foreground,
backgroundColor: colors.background
}}
onClick={onClick}
>{label}<button>
);
// later..
colors.foreground = 'blue';
// all buttons updated
See for more information the mobx-react
docs.
The simple rule of thumb is: all components that render observable data. If 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.
With @observer
there is no need to distinguish 'smart' components from 'dumb' components for the purpose of rendering.
It is still a good separation of concerns for where to handle events, make requests etc.
All components become responsible for updating when their own dependencies change.
Its overhead is neglectable and it makes sure that whenever you start using observable data the component will respond to it.
See this thread for more details.
observer
also prevents re-renderings when the props of the component have only shallowly changed, which makes a lot of sense if the data passed into the component is reactive.
This behavior is similar to React PureRender mixin, except that state changes are still always processed.
If a component provides its own shouldComponentUpdate
, that one takes precedence.
See for an explanation this github issue
React components usually render on a fresh stack, so that makes it often hard to figure out what caused a component to re-render.
When using mobx-react
you can define a new life cycle hook, componentWillReact
(pun intended) that will be triggered when a component will be scheduled to re-render because
data it observes has changed. This makes it easy to trace renders back to the action that caused the rendering.
import {observer} from "mobx-react";
@observer class TodoView extends React.Component {
componentWillReact() {
console.log("I will re-render, since the todo has changed!");
}
render() {
return <div>this.props.todo.title</div>;
}
}
componentWillReact
doesn't take argumentscomponentWillReact
won't fire before the initial render (usecomponentWillMount
instead)componentWillReact
won't fire when receiving new props or aftersetState
calls (usecomponentWillUpdate
instead)
See the relevant section.
In combination with @observer
you can use the MobX-React-DevTools, it shows exactly when your components are re-rendered and you can inspect the data dependencies of your components.
See the DevTools section.
- Observer only subscribe to the data structures that were actively used during the last render. This means that you cannot under-subscribe or over-subscribe. You can even use data in your rendering that will only be available at later moment in time. This is ideal for asynchronously loading data.
- You are not required to declare what data a component will use. Instead, dependencies are determined at runtime and tracked in a very fine-grained manner.
- Usually reactive components have no or little state, as it is often more convenient to encapsulate (view) state in objects that are shared with other component. But you are still free to use state.
@observer
implementsshouldComponentUpdate
in the same way asPureRenderMixin
so that children are not re-rendered unnecessary.- Reactive components sideways load data; parent components won't re-render unnecessarily even when child components will.
@observer
does not depend on React's context system.
Decorators are not supported by default when using TypeScript or Babel pending a definitive definition in the ES standard.
- For typescript, enable the
--experimentalDecorators
compiler flag or set the compiler optionexperimentalDecorators
totrue
intsconfig.json
(Recommended) - For babel5, make sure
--stage 0
is passed to the Babel CLI - For babel6, see the example configuration as suggested in this issue