@observer

Egghead.io lesson 1: observable & observer

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> )
    }
};

ReactDOM.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.

Gotcha: dereference values inside your components

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.

ES5 support

In ES5 environments, observer components can be simple declared using observer(React.createClass({ .... See also the syntax guide

Stateless function components

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>
);

Observable local component state

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 except componentWillUpdate and componentDidUpdate. If you need other React lifecycle methods, 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> )
    }
}

ReactDOM.render(<Timer />, document.body)

For more advantages of using observable local component state, see 3 reasons why I stopped using setState.

Connect components to provided stores using inject

Egghead.io lesson 8: inject stores with Provider

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 a list of store names to inject, which will make the stores available as props.

N.B. the syntax to inject stores has changed since mobx-react 4, always use inject(stores)(component) or @inject(stores) class Component.... Passing store names directly to observer is deprecated.

Example:

const colors = observable({
   foreground: '#000',
   background: '#fff'
});

const App = () =>
  <Provider colors={colors}>
     <app stuff... />
  </Provider>;

const Button = inject("colors")(observer(({ 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.

When to apply observer?

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 and PureComponent

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 PureComponent, 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

componentWillReact (lifecycle hook)

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 arguments
  • componentWillReact won't fire before the initial render (use componentWillMount instead)
  • componentWillReact for mobx-react@4+, the hook will fire when receiving new props and after setState calls

Optimizing components

See the relevant section.

MobX-React-DevTools

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.

Characteristics of observer components

  • 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 implements shouldComponentUpdate in the same way as PureComponent 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.
  • In mobx-react@4+, the props object and the state object of an observer component are automatically made observable to make it easier to create @computed properties that derive from props inside such a component. If you have a reaction (i.e. autorun) inside your @observer component that must not be re-evaluated when the specific props it uses don't change, be sure to derefence those specific props for use inside your reaction (i.e. const myProp = props.myProp). Otherwise, if you reference props.myProp inside the reaction, then a change in any of the props will cause the reaction to be re-evaluated. For a typical use case with React-Router, see this article.

Enabling ES6 decorators in your transpiler

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 option experimentalDecorators to true in tsconfig.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

results matching ""

    No results matching ""