Introduction
Simple, scalable state management
Introduction
MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP). The philosophy behind MobX is very simple:
Anything that can be derived from the application state, should be derived. Automatically.
which includes the UI, data serialization, server communication, etc.
React and MobX together are a powerful combination. React renders the application state by providing mechanisms to translate it into a tree of renderable components. MobX provides the mechanism to store and update the application state that React then uses.
Both React and MobX provide very optimal and unique solutions to common problems in application development. React provides mechanisms to optimally render UI by using a virtual DOM that reduces the number of costly DOM mutations. MobX provides mechanisms to optimally synchronize application state with your React components by using a reactive virtual dependency state graph that is only updated when strictly needed and is never stale.
Core concepts
MobX has only a few core concepts. The following snippets can be tried online using JSFiddle (or without ES6 and JSX).
Observable state
MobX adds observable capabilities to existing data structures like objects, arrays and class instances. This can simply be done by annotating your class properties with the @observable decorator (ES.Next), or by invoking the observable
or extendObservable
functions (ES5). See Language support for language-specific examples.
class Todo {
id = Math.random();
@observable title = "";
@observable finished = false;
}
class Todo {
constructor() {
this.id = Math.random()
extendObservable(this, {
title: "",
finished: false
})
}
}
function Todo() {
this.id = Math.random()
extendObservable(this, {
title: "",
finished: false
})
}
function createTodo() {
return observable({
id: Math.random(),
title: "",
finished: false
})
}
Using @observable
is like turning a value into a spreadsheet cell. But unlike spreadsheets, not only can these values be primitive values, but references, objects and arrays as well. You can even define your own observable data sources.
Computed values
With MobX you can define values that will be derived automatically when relevant data is modified. By using the @computed
decorator or by using parameterless functions as property values in extendObservable
.
class TodoList {
@observable todos = [];
@computed get unfinishedTodoCount() {
return this.todos.filter(todo => !todo.finished).length;
}
}
function TodoList() {
extendObservable(this, {
todos: [],
unfinishedTodoCount: function() {
return this.todos.filter(function (todo) {
return !todo.finished
}).length;
}
})
}
MobX will ensure that unfinishedTodoCount
is updated automatically when a todo is added or when one of the finished
properties is modified.
Computations like these can very well be compared with formulas in spreadsheet programs like MS Excel. They update automatically whenever, and only when, needed.
Reactions
Reactions are similar to a computed value, but instead of producing a new value, a reaction produces a side effect for things like printing to the console, making network requests, incrementally updating the React component tree to patch the DOM, etc. In short, reactions bridge reactive and imperative programming.
If you are using React, you can turn your (stateless function) components into reactive components by simply adding the @observer
decorator from the mobx-react
package onto them.
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {observer} from "mobx-react";
// ESNext decorator / JSX
@observer
class TodoListView extends Component {
render() {
return

{this.props.todoList.todos.map(todo =>
<TodoView todo={todo} key={todo.id} />
)}

Tasks left: {this.props.todoList.unfinishedTodoCount}

}
}
const TodoView = observer(({todo}) =>

<input
type="checkbox"
checked={todo.finished}
onClick={() => todo.finished = !todo.finished}
/>{todo.title}

);
const store = new TodoList();
ReactDOM.render(<TodoListView todoList={store} />, document.getElementById('mount'));
In ES5 a component declaration looks like this:
var TodoListView = observer(React.createClass({ }))
observer
turns React (function) components into derivations of the data they render.
Also, reactions can be created using the autorun
, autorunAsync
or when
functions to fit your specific situations.

 MobX will simply make sure the components are always re-rendered whenever needed, but also no more than that. So the onClick
handler in the above example will force the proper TodoView
to render, and it will cause the TodoListView
to render if the number of unfinished tasks has changed.
However, if you would remove the Tasks left
line (or put it into a separate component), the TodoListView
will no longer re-render when ticking a box. You can verify this yourself by changing the JSFiddle.
For an in-depth explanation about how MobX determines to which observables needs to be reacted, check out: Understanding what MobX reacts to
Actions
Unlike many flux frameworks, MobX is unopinionated about how user events should be handled.
- This can be done in a Flux like manner.
- Or by processing events using RxJS.
- Or by simply handling events in the most straightforward way possible, as demonstrated in the above
onClick
handler.
In the end it all boils down to: Somehow the state should be updated.
After updating the state MobX
will take care of the rest in an efficient, glitch-free manner. So simple statements, like below, are enough to automatically update the user interface.
There is no technical need for firing events, calling dispatcher or what more. A React component is in the end nothing more than a fancy representation of your state. A derivation that will be managed by MobX.
store.todos.push(
new Todo("Get Coffee"),
new Todo("Write simpler code")
);
store.todos[0].finished = true;
Nonetheless, MobX has an optional built-in concept of actions
.
Use them to your advantage; they will help you to structure your code better and make wise decisions about when and where state should be modified.
MobX: Simple and scalable
MobX is one of the least obtrusive libraries you can use for state management. That makes the MobX
approach not just simple, but very scalable as well:
Using classes and real references
With MobX you don't need to normalize your data. This makes the library very suitable for very complex domain models (At Mendix for example ~500 different domain classes in a single application).
Referential integrity is guaranteed
Since data doesn't need to be normalized, and MobX automatically tracks the relations between state and derivations, you get referential integrity for free. Rendering something that is accessed through three levels of indirection?
No problem, MobX will track them and re-render whenever one of the references changes. As a result staleness bugs are a thing of the past. As a programmer you might forget that changing some data might influence a seemingly unrelated component in a corner case. MobX won't forget.
Simpler actions are easier to maintain
As demonstrated above, modifying state when using MobX is very straightforward. You simply write down your intentions. MobX will take care of the rest.
Fine grained observability is efficient
MobX builds a graph of all the derivations in your application to find the least number of re-computations that is needed to prevent staleness. "Derive everything" might sound expensive, MobX builds a virtual derivation graph to minimize the number of recomputations needed to keep derivations in sync with the state.
In fact, when testing MobX at Mendix we found out that using this library to track the relations in our code is often a lot more efficient then pushing changes through our application by using handwritten events or "smart" selector based container components.
The simple reason is that MobX will establish far more fine grained 'listeners' on your data then you would do as a programmer.
Secondly MobX sees the causality between derivations so it can order them in such a way that no derivation has to run twice or introduces a glitch.
How that works? See this in-depth explanation of MobX.
Easy interoperability
MobX works plain javascript structures. Due to it's unobtrusiveness it works with most javascript libraries out of the box, without needing MobX specific library flavors.
So you can simple keep using your existing router-, data fetching and utility libraries like react-router
, director
, superagent
, lodash
etc.
For the same reason you can use it out of the box both server- and client side, in isomorphic applications and with react-native.
The result of this is that you often need to learn less new concepts when using MobX in comparison to other state management solutions.
Credits
MobX is inspired by reactive programming principles as found in spreadsheets. It is inspired by MVVM frameworks like in MeteorJS tracker, knockout and Vue.js. But MobX brings Transparent Functional Reactive Programming to the next level and provides a stand alone implementation. It implements TFRP in a glitch-free, synchronous, predictable and efficient manner.
A ton of credits for Mendix, for providing the flexibility and support to maintain MobX and the chance to proof the philosophy of MobX in a real, complex, performance critical applications.
And finally kudo's for all the people that believed in, tried and validated MobX.
Further resources and documentation
What others are saying...
After using #mobx for lone projects for a few weeks, it feels awesome to introduce it to the team. Time: 1/2, Fun: 2X
Working with #mobx is basically a continuous loop of me going “this is way too simple, it definitely won’t work” only to be proven wrong
Try react-mobx with es6 and you will love it so much that you will hug someone.
I have built big apps with MobX already and comparing to the one before that which was using Redux, it is simpler to read and much easier to reason about.
The #mobx is the way I always want things to be! It's really surprising simple and fast! Totally awesome! Don't miss it!
Contributing
- Feel free to send small pull requests. Please discuss new features or big changes in a GitHub issue first.
- Use
npm test
to run the basic test suite,npm run coverage
for the test suite with coverage andnpm run perf
for the performance tests.
Enabling decorators (optional)
TypeScript
Enable the compiler option experimentalDecorators
in tsconfig.json
or pass it as flag --experimentalDecorators
to the compiler.
Babel:
Install support for decorators: npm i --save-dev babel-plugin-transform-decorators-legacy
. And enable it in your .babelrc
file:
{
"presets": [
"es2015",
"stage-1"
],
"plugins": ["transform-decorators-legacy"]
}
Probably you have more plugins and presets in your .babelrc
already, note that the order is important and transform-decorators-legacy
should come as first.
Bower support
Bower support is available through the infamous npmcdn.com:
bower install https://npmcdn.com/mobx/bower.zip
Then use lib/mobx.umd.js
or lib/mobx.umd.min.js
MobX was formerly known as Mobservable.
See the changelog for all the details about mobservable
to mobx
.
Donating
Was MobX key in making your project a success? Share the victory by using the donate button! MobX is developed largely in free time, so any ROI is appreciated :-). If you leave a name it will be added to the sponsors list.
1. Introduction to MobX
The Gist of MobX
So far it all might sound a bit fancy, but making an app reactive using MobX boils down to just these three steps:
1. Define your state and make it observable
Store state in any data structure you like; objects, array, classes.
Cyclic data structures, references, it doesn't matter.
Just make sure that all properties that you want to change over time are marked by mobx
to make them observable.
import {observable} from 'mobx';
var appState = observable({
timer: 0
});
2. Create a view that responds to changes in the State
We didn't make our appState
observable just for nothing;
you can now create views that automatically update whenever relevant data in the appState
changes.
MobX will find the minimal way to update your views.
This single fact saves you tons of boilerplate and is wickedly efficient.
Generally speaking any function can become a reactive view that observes its data, and MobX can be applied in any ES5 conformant JavaScript environment. But here is an (ES6) example of a view in the form of a React component.
import {observer} from 'mobx-react';
@observer
class TimerView extends React.Component {
render() {
return (<button onClick={this.onReset.bind(this)}>
Seconds passed: {this.props.appState.timer}
</button>);
}
onReset () {
this.props.appState.resetTimer();
}
};
React.render(<TimerView appState={appState} />, document.body);
(For the implementation of resetTimer
function see the next section)
3. Modify the State
The third relevant thing to do is modifying the state. That is what your app is about after all. Unlike many other frameworks, MobX doesn't dictate you how to do this. There are best practices, but the key thing to remember is: MobX helps you to do things in a simple straightforward way.
The following code will alter your data every second, and the UI will update automatically when needed.
No explicit relations are defined in either in the controller functions that change the state or in the views that should update.
Decorating your state and views with observable
is enough for MobX to detect all relationships.
Here are two examples of changing the state:
appState.resetTimer = action(function reset() {
appState.timer = 0;
});
setInterval(action(function tick() {
appState.timer += 1;
}), 1000);
The action
wrapper is only needed when using MobX in strict mode (by default off), but will help you to better structure applications and expresses the intention of a function to modify state.
Feel free to try this example on JSFiddle or by cloning the MobX boilerplate project
Concepts & Principles
Concepts
MobX distinguishes the following concepts in your application. You saw them in the previous gist, but let's dive into them in a bit more detail.
1. State
State is the data that drives your application. Usually there is domain specific state like a list of todo items and there is view state such as the currently selected element. Remember, state is like spreadsheets cells that hold a value.
2. Derivations
Anything that can be derived from the state without any further interaction is a derivation. Derivations exist in many forms:
- The user interface.
- Derived data, such as the amount of todos left.
- Backend integrations like sending changes to the server.
MobX distinguishes two kind of derivations:
- Computed values. These are values that can always be derived from the current observable state using a pure function.
- Reactions. Reactions are side effects that need to happen automatically if the state changes. These are needed to as bridge between imperative and reactive programming. Or to make it more clear, they are ultimately needed to achieve I/O.
People starting with MobX tend to use reactions too often.
The golden rule is: if you want to create a value based on the current state, use computed
.
Back to the spreadsheet analogy, formulas are derivations that compute a value. But for you as a user to be able to see it on the screen a reaction is needed that repaints part of the GUI.
3. Actions
An action is any piece of code that changes the state. User events, backend data pushes, scheduled events etc. An action is like a user that enters a new value in a spreadsheet cell.
Actions can be defined explicitly in MobX to help you to structure code more clearly. If MobX is used in strict mode, MobX will enforce that no state can be modified outside actions.
Principles
MobX supports an uni-directional data flow where actions changes the state, which in turn updates all affected views.
All Derivations are updated automatically and atomically when the state changes. As a result it is not possible to ever observe intermediate values.
All Derivations are updated synchonously by default. This means that for example actions can safely inspect a computed value directly after altering the state.
Computed values are updated lazily. Any computed value that is not actively in use will not be updated until it is needed for a side effect (I/O). If a view is no longer in use it will be garbage collected automatically.
All Computed values should be pure. They are not supposed to change state.
Illustration
The following listing illustrates the above concepts and principles:
import {observable, autorun} from 'mobx';
var todoStore = observable({
todos: [],
completedCount: function() {
return this.todos.filter(todo => todo.completed).length;
}
});
autorun(function() {
console.log("Completed %d of %d items",
todoStore.completedCount,
todoStore.todos.length
);
});
todoStore.todos[0] = {
title: "Take a walk",
completed: false
};
todoStore.todos[0].completed = true;
In the 10 minute introduction to MobX and React you can dive deeper into this example and build a user interface using React around it.
The Gist of MobX
So far it all might sound a bit fancy, but making an app reactive using MobX boils down to just these three steps:
1. Define your state and make it observable
Store state in any data structure you like; objects, array, classes.
Cyclic data structures, references, it doesn't matter.
Just make sure that all properties that you want to change over time are marked by mobx
to make them observable.
import {observable} from 'mobx';
var appState = observable({
timer: 0
});
2. Create a view that responds to changes in the State
We didn't make our appState
observable just for nothing;
you can now create views that automatically update whenever relevant data in the appState
changes.
MobX will find the minimal way to update your views.
This single fact saves you tons of boilerplate and is wickedly efficient.
Generally speaking any function can become a reactive view that observes its data, and MobX can be applied in any ES5 conformant JavaScript environment. But here is an (ES6) example of a view in the form of a React component.
import {observer} from 'mobx-react';
@observer
class TimerView extends React.Component {
render() {
return (<button onClick={this.onReset.bind(this)}>
Seconds passed: {this.props.appState.timer}
</button>);
}
onReset () {
this.props.appState.resetTimer();
}
};
React.render(<TimerView appState={appState} />, document.body);
(For the implementation of resetTimer
function see the next section)
3. Modify the State
The third relevant thing to do is modifying the state. That is what your app is about after all. Unlike many other frameworks, MobX doesn't dictate you how to do this. There are best practices, but the key thing to remember is: MobX helps you to do things in a simple straightforward way.
The following code will alter your data every second, and the UI will update automatically when needed.
No explicit relations are defined in either in the controller functions that change the state or in the views that should update.
Decorating your state and views with observable
is enough for MobX to detect all relationships.
Here are two examples of changing the state:
appState.resetTimer = action(function reset() {
appState.timer = 0;
});
setInterval(action(function tick() {
appState.timer += 1;
}), 1000);
The action
wrapper is only needed when using MobX in strict mode (by default off), but will help you to better structure applications and expresses the intention of a function to modify state.
Feel free to try this example on JSFiddle or by cloning the MobX boilerplate project
Concepts & Principles
Concepts
MobX distinguishes the following concepts in your application. You saw them in the previous gist, but let's dive into them in a bit more detail.
1. State
State is the data that drives your application. Usually there is domain specific state like a list of todo items and there is view state such as the currently selected element. Remember, state is like spreadsheets cells that hold a value.
2. Derivations
Anything that can be derived from the state without any further interaction is a derivation. Derivations exist in many forms:
- The user interface.
- Derived data, such as the amount of todos left.
- Backend integrations like sending changes to the server.
MobX distinguishes two kind of derivations:
- Computed values. These are values that can always be derived from the current observable state using a pure function.
- Reactions. Reactions are side effects that need to happen automatically if the state changes. These are needed to as bridge between imperative and reactive programming. Or to make it more clear, they are ultimately needed to achieve I/O.
People starting with MobX tend to use reactions too often.
The golden rule is: if you want to create a value based on the current state, use computed
.
Back to the spreadsheet analogy, formulas are derivations that compute a value. But for you as a user to be able to see it on the screen a reaction is needed that repaints part of the GUI.
3. Actions
An action is any piece of code that changes the state. User events, backend data pushes, scheduled events etc. An action is like a user that enters a new value in a spreadsheet cell.
Actions can be defined explicitly in MobX to help you to structure code more clearly. If MobX is used in strict mode, MobX will enforce that no state can be modified outside actions.
Principles
MobX supports an uni-directional data flow where actions changes the state, which in turn updates all affected views.
All Derivations are updated automatically and atomically when the state changes. As a result it is not possible to ever observe intermediate values.
All Derivations are updated synchonously by default. This means that for example actions can safely inspect a computed value directly after altering the state.
Computed values are updated lazily. Any computed value that is not actively in use will not be updated until it is needed for a side effect (I/O). If a view is no longer in use it will be garbage collected automatically.
All Computed values should be pure. They are not supposed to change state.
Illustration
The following listing illustrates the above concepts and principles:
import {observable, autorun} from 'mobx';
var todoStore = observable({
todos: [],
completedCount: function() {
return this.todos.filter(todo => todo.completed).length;
}
});
autorun(function() {
console.log("Completed %d of %d items",
todoStore.completedCount,
todoStore.todos.length
);
});
todoStore.todos[0] = {
title: "Take a walk",
completed: false
};
todoStore.todos[0].completed = true;
In the 10 minute introduction to MobX and React you can dive deeper into this example and build a user interface using React around it.
API overview
MobX Api Reference
Core API
The most important MobX api's. Understanding observable
, computed
, reactions
and actions
is enough to master MobX and use it in your applications!
Creating observables
observable
Usage:
observable(value)
@observable classProperty = value
Observable values can be JS primitives, references, plain objects, class instances, arrays and maps. The following conversion rules are applied, but can be fine-tuned by using modifiers. See below.
- If value is wrapped in the modifier
asMap
: a new Observable Map will be returned. Observable maps are very useful if you don't want to react just to the change of a specific entry, but also to the addition or removal of entries. - If value is an array, a new Observable Array will be returned.
- If value is an object without prototype, all its current properties will be made observable. See Observable Object
- If value is an object with a prototype, a JavaScript primitive or function, a Boxed Observable will be returned. MobX will not make objects with a prototype automatically observable; as that is the responsibility of it's constructor function. Use
extendObservable
in the constructor, or@observable
in it's class definition instead.
These rules might seem complicated at first sight, but you will notice that in practice they are very intuitive to work with. Some notes:
- To create dynamically keyed objects use the
asMap
modifier! Only initially existing properties on an object will be made observable, although new ones can be added usingextendObservable
. - To use the
@observable
decorator, make sure that decorators are enabled in your transpiler (babel or typescript). - By default making a data structure observable is infective; that means that
observable
is applied automatically to any value that is contained by the data structure, or will be contained by the data structure in the future. This behavior can be changed by using modifiers.
extendObservable
Usage: extendObservable(target, propertyMap)
. For each key/value pair in propertyMap
a (new) observable property will be introduced on the target object.
This can be used in constructor functions to introduce observable properties without using decorators.
If a value of the propertyMap
is an argumentless function, a computed property will be introduced.
«details»
Computed values
Usage:
computed(() => expression)
@computed get classProperty() { return expression; }
Creates a computed property. The expression
should not have side effects but return a value.
The expression will automatically be re-evaluated if any observables it uses changes, but only if it is in use by some reaction.
«details»
Actions
Any application has actions. Actions are anything that modify the state.
With MobX you can make it explicit in your code where your actions live by marking them.
Actions helps you to structure your code better.
It is advised to use them on any function that modifies observables or has side effects.
action
also provides useful debugging information in combination with the devtools.
Note: using action
is mandatory when strict mode is enabled, see useStrict
.
«details»
Usage:
action(fn)
action(name, fn)
@action classMethod
@action(name) classMethod
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
For one-time-actions runInAction(name?, fn, scope?)
can be used, which is sugar for action(name, fn, scope)()
.
Reactions
Computed values are values that react automatically to state changes.
Reactions are side effects that react automatically to state changes.
Reactions can be used to ensure that a certain side effect (mainly I/O) is automatically executed when relevant state changes, like logging, network requests etc.
The most commonly used reaction is the observer
decorator for React components (see above).
observer
Can be used as higher order component around a React component.
The component will then automatically re-render if any of the observables used in the render
function of the component has changed.
Note that observer
is provided by the "mobx-react"
package and not by "mobx"
itself.
«details»
Usage:
observer(React.createClass({ ... }))
observer((props, context) => ReactElement)
observer(class MyComponent extends React.Component { ... })
@observer class MyComponent extends React.Component { ... })
autorun
Usage: autorun(debugname?, () => { sideEffect })
. Autorun runs the provided sideEffect
and tracks which observable state is accessed while running the side effect.
Whenever one of the used observables is changed in the future, the same sideEffect will be run again.
Returns a disposer function to cancel the side effect. «details»
when
Usage: when(debugname?, () => condition, () => { sideEffect })
.
The condition expression will react automatically to any observables is uses.
As soon as the expression returns true the sideEffect function will be invoked, but only once.
when
returns a disposer to prematurely cancel the whole thing. «details»
autorunAsync
Usage: autorunAsync(debugname?, () => { sideEffect }, delay)
. Similar to autorun
, but the sideEffect will be delayed and debounced with the given delay
.
«details»
reaction
Usage: reaction(debugname?, () => data, data => { sideEffect }, fireImmediately = false, delay = 0)
.
A variation on autorun
that gives more fine-grained control on which observables that will be tracked.
It takes two function, the first one is tracked and returns data that is used as input for the second one, the side effect.
Unlike autorun
the side effect won't be run initially, and any observables that are accessed while executing the side effect will not be tracked.
The side effect can be debounced, just like autorunAsync
. «details»
Modifiers for observable
By default observable
is applied recursively and to values that are assigned in the future as well.
Modifiers can be used to influence how observable
treats specific values.
asMap
: This is the most important modifier. Instead of creating an object with observable properties, an Observable Map is created instead. The main difference with observable objects is that the addition and removal of properties can be easily observed. UseasMap
if you want a map like data structure where the keys will change over time.asFlat
: Don't applyobservable
recursively. The passed object / collection itself will be observable, but the values in it won't. This disables the possibility to deeply observe objects.asReference
: Use the passed in value verbatim, just create an observable reference to the object.asStructure
: When new values are assigned, ignore the new value if it structurally equal to the previous value.
Utilities
Here are some utilities that might make working with observable objects or computed values more convenient. More, less trivial utilities can be found in the * mobx-utils package.
Provider
(mobx-react
package)
Can be used to pass stores to child components using React's context mechanism. See the mobx-react
docs.
inject
(mobx-react
package)
Higher order component and counterpart of Provider
. Can be used to pick stores from React's context and pass it as props to the target component. Usage:
inject("store1", "store2")(observer(MyComponent))
@inject("store1", "store2") @observer MyComponent
- `@inject((stores, props, context) => props) @observer MyComponent
@observer(["store1", "store2"]) MyComponent
is a shorthand for the the@inject() @observer
combo.
toJS
Usage: toJS(observableDataStructure)
. Converts observable data structures back to plain javascript objects, ignoring computed values. «details»
isObservable
Usage: isObservable(thing, property?)
. Returns true if the given thing, or the property
of the given thing is observable.
Works for all observables, computed values and disposer functions of reactions. «details»
isObservableObject|Array|Map
Usage: isObservableObject(thing)
, isObservableArray(thing)
, isObservableMap(thing)
. Returns true
if.., well, do the math.
isAction
Usage: isAction(func)
. Returns true if the given function is wrapped / decorated with action
.
createTransformer
Usage: createTransformer(transformation: A => B, onCleanup?): A = B
.
Can be used to make functions that transforms one value into another value reactive and memoized.
It behaves similar to computed and can be used for advanced patterns like very efficient array maps, map reduce or computed values that are not part of an object.
«details»
intercept
Usage: intercept(object, property?, interceptor)
.
Api that can be used to intercept changes before they are applied to an observable api. Useful for validation, normalization or cancellation.
«details»
observe
Usage: observe(object, property?, listener, fireImmediately = false)
Low-level api that can be used to observe a single observable value.
«details»
useStrict
Usage: useStrict(boolean)
.
Enables / disables strict mode globally.
In strict mode, it is not allowed to change any state outside of an action
.
See also extras.allowStateChanges
.
Development utilities
The following api's might come in handy if you want to build cool tools on top of MobX or if you want to inspect the internal state of MobX
"mobx-react-devtools"
package
The mobx-react-devtools is a powerful package that helps you to investigate the performance and dependencies of your react components.
Also has a powerful logger utility based on spy
. «details»
spy
Usage: spy(listener)
.
Registers a global spy listener that listens to all events that happen in MobX.
It is similar to attaching an observe
listener to all observables at once, but also notifies about running (trans/re)actions and computations.
Used for example by the mobx-react-devtools
.
«details»
whyRun
Usage:
whyRun()
whyRun(Reaction object / ComputedValue object / disposer function)
whyRun(object, "computed property name")
whyRun
is a small utility that can be used inside computed value or reaction (autorun
, reaction
or the render
method of an observer
React component)
and prints why the derivation is currently running, and under which circumstances it will run again.
This should help to get a deeper understanding when and why MobX runs stuff, and prevent some beginner mistakes.
extras.getAtom
Usage: getAtom(thing, property?)
.
Returns the backing Atom of a given observable object, property, reaction etc.
extras.getDebugName
Usage: getDebugName(thing, property?)
Returns a (generated) friendly debug name of an observable object, property, reaction etc. Used by for example the mobx-react-devtools
.
extras.getDependencyTree
Usage: getDependencyTree(thing, property?)
.
Returns a tree structure with all observables the given reaction / computation currently depends upon.
extras.getObserverTree
Usage: getObserverTree(thing, property?)
.
Returns a tree structure with all reactions / computations that are observing the given observable.
extras.isSpyEnabled
Usage: isSpyEnabled()
. Returns true if at least one spy is active
extras.spyReport
Usage: spyReport({ type: "your type", «details» data})
. Emit your own custom spy event.
extras.spyReportStart
Usage: spyReportStart({ type: "your type", «details» data})
. Emit your own custom spy event. Will start a new nested spy event group which should be closed using spyReportEnd()
extras.spyReportEnd
Usage: spyReportEnd()
. Ends the current spy group that was started with extras.spyReportStart
.
"mobx-react"
development hooks
The mobx-react
package exposes the following additional api's that are used by the mobx-react-devtools
:
trackComponents()
: enables the tracking ofobserver
based React componentsrenderReporter.on(callback)
: callback will be invoked on each rendering of anobserver
enabled React component, with timing information etccomponentByNodeRegistery
: ES6 WeakMap that maps from DOMNode to aobserver
based React component instance
Internal functions
The following methods are all used internally by MobX, and might come in handy in rare cases. But usually MobX offers more declarative alternatives to tackle the same problem. They might come in handy though if you try to extend MobX
transaction
Usage: transaction(() => { block })
.
Low-level api that can be used to batch state changes.
State changes made inside the block won't cause any computations or reactions to run until the end of the block is reached.
Nonetheless inspecting a computed value inside a transaction block will still return a consistent value.
It is recommended to use action
instead, which uses transaction
internally.
«details»
untracked
Usage: untracked(() => { block })
.
Low-level api that might be useful inside reactions and computations.
Any observables accessed in the block
won't cause the reaction / compuations to be recomputed automatically.
However it is recommended to use action
instead, which uses untracked
internally.
«details»
Atom
Utility class that can be used to create your own observable data structures and hook them up to MobX. Used internally by all observable data types. «details»
Reaction
Utility class that can be used to create your own reactions and hook them up to MobX.
Used internally by autorun
, reaction
(function) etc.
«details»
extras.allowStateChanges
Usage: allowStateChanges(allowStateChanges, () => { block })
.
Can be used to (dis)allow state changes in a certain function.
Used internally by action
to allow changes, and by computed
and observer
to disallow state changes.
extras.resetGlobalState
Usage: resetGlobalState()
.
Resets MobX internal global state. MobX by defaults fails fast if an exception occurs inside a computation or reaction and refuses to run them again.
This function resets MobX to the zero state. Existing spy
listeners and the current value of strictMode will be preserved though.
Functions that might get deprecated
map
Will probably by deprecated, use observable(asMap())
instead. Usage: map()
, map(keyValueObject)
, map(entries)
.
Returns an observable, largely ES6 compliant Map data structure.
This is useful if you want to store data based on string keys.
For the full api of the returned ObservableMap
see Observable maps.
«details»
expr
Usage: expr(() => someExpression
. Just a shorthand for computed(() => someExpression).get()
.
expr
is useful in some rare cases to optimize another computed function or reaction.
In general it is simpler and better to just split the function in multiple smaller computed's to achieve the same effect.
«details»
observable | MobX
Usage:
observable(value)
@observable classProperty = value
Observable values can be JS primitives, references, plain objects, class instances, arrays and maps. The following conversion rules are applied, but can be fine-tuned by using modifiers. See below.
- If value is wrapped in the modifier
asMap
: a new Observable Map will be returned. Observable maps are very useful if you don't want to react just to the change of a specific entry, but also to the addition or removal of entries. - If value is an array, a new Observable Array will be returned.
- If value is an object without prototype, all its current properties will be made observable. See Observable Object
- If value is an object with a prototype, a JavaScript primitive or function, a Boxed Observable will be returned. MobX will not make objects with a prototype automatically observable; as that is the responsibility of its constructor function. Use
extendObservable
in the constructor, or@observable
in its class definition instead.
These rules might seem complicated at first sight, but you will notice that in practice they are very intuitive to work with. Some notes:
- To create dynamically keyed objects use the
asMap
modifier! Only initially existing properties on an object will be made observable, although new ones can be added usingextendObservable
. - To use the
@observable
decorator, make sure that decorators are enabled in your transpiler (babel or typescript). - By default, making a data structure observable is infective; that means that
observable
is applied automatically to any value that is contained by the data structure, or will be contained by the data structure in the future. This behavior can be changed by using modifiers.
Some examples:
const map = observable(asMap({ key: "value"}));
map.set("key", "new value");
const list = observable([1, 2, 4]);
list[2] = 3;
const person = observable({
firstName: "Clive Staples",
lastName: "Lewis"
});
person.firstName = "C.S.";
const temperature = observable(20);
temperature.set(25);
@observable
Decorator that can be used on ES7- or TypeScript class properties to make them observable. The @observable can be used on instance fields and property getters. This offers fine-grained control on which parts of your object become observable.
import {observable} from "mobx";
class OrderLine {
@observable price:number = 0;
@observable amount:number = 1;
constructor(price) {
this.price = price;
}
@computed get total() {
return this.price * this.amount;
}
}
If your environment doesn't support decorators or field initializers,
@observable key = value;
is sugar for extendObservable(this, { key: value })
Enumerability: properties decorator with @observable
are enumerable, but defined on the class prototype and not on the class instances.
In other words:
const line = new OrderLine();
console.log("price" in line)
console.log(line.hasOwnProperty("price"))
The @observable
decorator can be combined with modifiers like asStructure
:
@observable position = asStructure({ x: 0, y: 0})
Enabling 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 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
(@)computed
@computed
Decorator that can be used on ES6 or TypeScript derivable class properties to make them observable.
The @computed
can only be used on get
functions for instance properties.
Use @computed
if you have a value that can be derived in a pure manner from other observables.
Don't confuse @computed
with autorun
. They are both reactively invoked expressions,
but use @computed
if you want to reactively produce a new value that can be used by other observers and
autorun
if you don't want to produce a new value but rather invoke some imperative code like logging, network requests etc.
Computed properties can be optimized away in many cases by MobX as they are assumed to be pure. So they will not be invoked when their input parameters didn't modifiy or if they are not observed by some other computed value or autorun.
import {observable, computed} from "mobx";
class OrderLine {
@observable price:number = 0;
@observable amount:number = 1;
constructor(price) {
this.price = price;
}
@computed get total() {
return this.price * this.amount;
}
}
If your environment doesn't support decorators or field initializers,
@computed get funcName() { }
is sugar for extendObservable(this, { funcName: func })
@computed
can be parameterized. @computed({asStructure: true})
makes sure that the result of a derivation is compared structurally instead of referentially with its preview value. This makes sure that observers of the computation don't re-evaluate if new structures are returned that are structurally equal to the original ones. This is very useful when working with point, vector or color structures for example. It behaves the same as the asStructure
modifier for observable values.
@computed
properties are not enumerable.
Enabling 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 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
computed(expression)
computed
can also be invoked directly as function.
Just like observable(primitive value)
it will create a stand-alone observable.
Use .get()
on the returned object to get the current value of the computation, or .observe(callback)
to observe it's changes.
Example:
import {observable, computed} from "mobx";
var name = observable("John");
var age = observable(42);
var showAge = observable(false);
var labelText = computed(() =>
showAge.get() ? `${name.get()} (age: ${age.get()})` : name.get();
);
var disposer = labelText.observe(newLabel => console.log(newLabel));
name.set("Dave");
age.set(21);
showAge.set(true);
age.set(42);
disposer();
name.set("Matthew");
console.log(labelText.get());
Note how the function now automatically reacts to data changes,
but only if they occurred in data that was actually used to produce the output.
Hence the first change to age
didn't result in a re-evaluation of the labelText
function.
MobX will automatically determine whether the function should run eagerly or lazily based on how the views are used throughout your application,
so make sure your code doesn't rely on any side effects in those functions.
These two forms of observable
, one for primitives and references, and one for functions, form the core of MobX.
The rest of the API is just syntactic sugar around these two core operations.
Nonetheless, you will rarely use these forms; using objects is just a tat more convenient.
autorun | MobX
mobx.autorun
can be used in those cases where you want to create a reactive function that will never have observers itself.
This is usually the case when you need to bridge from reactive to imperative code, for example for logging, persistence or UI-updating code.
When autorun
is used, the provided function will always be triggered when one of its dependencies changes.
In contrast, computed(function)
creates functions that only re-evaluate if it has
observers on its own, otherwise its value is considered to be irrelevant.
As a rule of thumb: use autorun
if you have a function that should run automatically but that doesn't result in a new value.
Use computed
for everything else. Autoruns are about initiating effects, not about producing new values.
If a string is passed as first argument to autorun
, it will be used as debug name.
The function passed to autorun will receive one argument when invoked, the current reaction (autorun), which can be used to dispose the autorun during execution.
Just like the @observer
decorator/function, autorun
will only observe data that is used during the execution of the provided function.
var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));
var disposer = autorun(() => console.log(sum.get()));
numbers.push(4);
disposer();
numbers.push(5);
(@)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 (Seconds passed: { this.props.timerData.secondsPassed }  )
}
})
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.
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 }) =>
Seconds passed: { timerData.secondsPassed }
)
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 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 (Seconds passed: { this.secondsPassed }  )
}
})
React.render(<Timer />, document.body)
For more advantages of using observable local component state, see 3 reasons why I stopped using setState
.
Connect observer
to stores
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>
)
colors.foreground = 'blue';
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 PureRenderMixin
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
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
this.props.todo.title

}
}
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)
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
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.
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 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
action | MobX
Usage:
action(fn)
action(name, fn)
@action classMethod
@action(name) classMethod
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
Any application has actions. Actions are anything that modify the state.
With MobX you can make it explicit in your code where your actions live by marking them.
Actions help you to structure your code better.
It takes a function and returns it after wrapping it with untracked
, transaction
and allowStateChanges
.
It is advised to use them on any function that modifies observables or has side effects.
action
also provides useful debugging information in combination with the devtools.
Note: using action
is mandatory when strict mode is enabled, see useStrict
. Using the @action
decorator with ES 5.1 setters (i.e. @action set propertyName
) is not supported.
For an extensive introduction to action
see also the MobX 2.2 release notes.
Two example actions from the contact-list
project:
@action createRandomContact() {
this.pendingRequestCount++;
superagent
.get('https://randomuser.me/api/')
.set('Accept', 'application/json')
.end(action("createRandomContact-callback", (error, results) => {
if (error)
console.error(error);
else {
const data = JSON.parse(results.text).results[0];
const contact = new Contact(this, data.dob, data.name, data.login.username, data.picture)
contact.addTag('random-user');
this.contacts.push(contact);
this.pendingRequestCount--;
}
}));
}
action
only affects the currently running function, not functions that are scheduled (but not invoked) by the current function!
This means that if you have a setTimeout
, promise.then
or async
construction, and in that callback some more state is changed, those callbacks should be wrapped in action
as well!
This is demonstrated above with the "createRandomContact-callback"
action.
If you use async
/ await
, this is a bit trickier as you cannot just wrap the async function body in action
.
In this situation runInAction
can come in handy, wrap this around the places where you intend to update the state.
(But don't make await
calls in these blocks).
Example:
@action updateDocument = async () => {
const data = await fetchDataFromUrl();
runInAction("update state after fetching data", () => {
this.data.replace(data);
this.isSaving = true;
})
}
The usage of runInAction
is: runInAction(name?, fn, scope?)
.
observable | MobX
Usage:
observable(value)
@observable classProperty = value
Observable values can be JS primitives, references, plain objects, class instances, arrays and maps. The following conversion rules are applied, but can be fine-tuned by using modifiers. See below.
- If value is wrapped in the modifier
asMap
: a new Observable Map will be returned. Observable maps are very useful if you don't want to react just to the change of a specific entry, but also to the addition or removal of entries. - If value is an array, a new Observable Array will be returned.
- If value is an object without prototype, all its current properties will be made observable. See Observable Object
- If value is an object with a prototype, a JavaScript primitive or function, a Boxed Observable will be returned. MobX will not make objects with a prototype automatically observable; as that is the responsibility of its constructor function. Use
extendObservable
in the constructor, or@observable
in its class definition instead.
These rules might seem complicated at first sight, but you will notice that in practice they are very intuitive to work with. Some notes:
- To create dynamically keyed objects use the
asMap
modifier! Only initially existing properties on an object will be made observable, although new ones can be added usingextendObservable
. - To use the
@observable
decorator, make sure that decorators are enabled in your transpiler (babel or typescript). - By default, making a data structure observable is infective; that means that
observable
is applied automatically to any value that is contained by the data structure, or will be contained by the data structure in the future. This behavior can be changed by using modifiers.
Some examples:
const map = observable(asMap({ key: "value"}));
map.set("key", "new value");
const list = observable([1, 2, 4]);
list[2] = 3;
const person = observable({
firstName: "Clive Staples",
lastName: "Lewis"
});
person.firstName = "C.S.";
const temperature = observable(20);
temperature.set(25);
@observable
Decorator that can be used on ES7- or TypeScript class properties to make them observable. The @observable can be used on instance fields and property getters. This offers fine-grained control on which parts of your object become observable.
import {observable} from "mobx";
class OrderLine {
@observable price:number = 0;
@observable amount:number = 1;
constructor(price) {
this.price = price;
}
@computed get total() {
return this.price * this.amount;
}
}
If your environment doesn't support decorators or field initializers,
@observable key = value;
is sugar for extendObservable(this, { key: value })
Enumerability: properties decorator with @observable
are enumerable, but defined on the class prototype and not on the class instances.
In other words:
const line = new OrderLine();
console.log("price" in line)
console.log(line.hasOwnProperty("price"))
The @observable
decorator can be combined with modifiers like asStructure
:
@observable position = asStructure({ x: 0, y: 0})
Enabling 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 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
(@)computed
@computed
Decorator that can be used on ES6 or TypeScript derivable class properties to make them observable.
The @computed
can only be used on get
functions for instance properties.
Use @computed
if you have a value that can be derived in a pure manner from other observables.
Don't confuse @computed
with autorun
. They are both reactively invoked expressions,
but use @computed
if you want to reactively produce a new value that can be used by other observers and
autorun
if you don't want to produce a new value but rather invoke some imperative code like logging, network requests etc.
Computed properties can be optimized away in many cases by MobX as they are assumed to be pure. So they will not be invoked when their input parameters didn't modifiy or if they are not observed by some other computed value or autorun.
import {observable, computed} from "mobx";
class OrderLine {
@observable price:number = 0;
@observable amount:number = 1;
constructor(price) {
this.price = price;
}
@computed get total() {
return this.price * this.amount;
}
}
If your environment doesn't support decorators or field initializers,
@computed get funcName() { }
is sugar for extendObservable(this, { funcName: func })
@computed
can be parameterized. @computed({asStructure: true})
makes sure that the result of a derivation is compared structurally instead of referentially with its preview value. This makes sure that observers of the computation don't re-evaluate if new structures are returned that are structurally equal to the original ones. This is very useful when working with point, vector or color structures for example. It behaves the same as the asStructure
modifier for observable values.
@computed
properties are not enumerable.
Enabling 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 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
computed(expression)
computed
can also be invoked directly as function.
Just like observable(primitive value)
it will create a stand-alone observable.
Use .get()
on the returned object to get the current value of the computation, or .observe(callback)
to observe it's changes.
Example:
import {observable, computed} from "mobx";
var name = observable("John");
var age = observable(42);
var showAge = observable(false);
var labelText = computed(() =>
showAge.get() ? `${name.get()} (age: ${age.get()})` : name.get();
);
var disposer = labelText.observe(newLabel => console.log(newLabel));
name.set("Dave");
age.set(21);
showAge.set(true);
age.set(42);
disposer();
name.set("Matthew");
console.log(labelText.get());
Note how the function now automatically reacts to data changes,
but only if they occurred in data that was actually used to produce the output.
Hence the first change to age
didn't result in a re-evaluation of the labelText
function.
MobX will automatically determine whether the function should run eagerly or lazily based on how the views are used throughout your application,
so make sure your code doesn't rely on any side effects in those functions.
These two forms of observable
, one for primitives and references, and one for functions, form the core of MobX.
The rest of the API is just syntactic sugar around these two core operations.
Nonetheless, you will rarely use these forms; using objects is just a tat more convenient.
autorun | MobX
mobx.autorun
can be used in those cases where you want to create a reactive function that will never have observers itself.
This is usually the case when you need to bridge from reactive to imperative code, for example for logging, persistence or UI-updating code.
When autorun
is used, the provided function will always be triggered when one of its dependencies changes.
In contrast, computed(function)
creates functions that only re-evaluate if it has
observers on its own, otherwise its value is considered to be irrelevant.
As a rule of thumb: use autorun
if you have a function that should run automatically but that doesn't result in a new value.
Use computed
for everything else. Autoruns are about initiating effects, not about producing new values.
If a string is passed as first argument to autorun
, it will be used as debug name.
The function passed to autorun will receive one argument when invoked, the current reaction (autorun), which can be used to dispose the autorun during execution.
Just like the @observer
decorator/function, autorun
will only observe data that is used during the execution of the provided function.
var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));
var disposer = autorun(() => console.log(sum.get()));
numbers.push(4);
disposer();
numbers.push(5);
(@)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 (Seconds passed: { this.props.timerData.secondsPassed }  )
}
})
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.
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 }) =>
Seconds passed: { timerData.secondsPassed }
)
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 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 (Seconds passed: { this.secondsPassed }  )
}
})
React.render(<Timer />, document.body)
For more advantages of using observable local component state, see 3 reasons why I stopped using setState
.
Connect observer
to stores
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>
)
colors.foreground = 'blue';
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 PureRenderMixin
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
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
this.props.todo.title

}
}
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)
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
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.
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 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
action | MobX
Usage:
action(fn)
action(name, fn)
@action classMethod
@action(name) classMethod
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
Any application has actions. Actions are anything that modify the state.
With MobX you can make it explicit in your code where your actions live by marking them.
Actions help you to structure your code better.
It takes a function and returns it after wrapping it with untracked
, transaction
and allowStateChanges
.
It is advised to use them on any function that modifies observables or has side effects.
action
also provides useful debugging information in combination with the devtools.
Note: using action
is mandatory when strict mode is enabled, see useStrict
. Using the @action
decorator with ES 5.1 setters (i.e. @action set propertyName
) is not supported.
For an extensive introduction to action
see also the MobX 2.2 release notes.
Two example actions from the contact-list
project:
@action createRandomContact() {
this.pendingRequestCount++;
superagent
.get('https://randomuser.me/api/')
.set('Accept', 'application/json')
.end(action("createRandomContact-callback", (error, results) => {
if (error)
console.error(error);
else {
const data = JSON.parse(results.text).results[0];
const contact = new Contact(this, data.dob, data.name, data.login.username, data.picture)
contact.addTag('random-user');
this.contacts.push(contact);
this.pendingRequestCount--;
}
}));
}
action
only affects the currently running function, not functions that are scheduled (but not invoked) by the current function!
This means that if you have a setTimeout
, promise.then
or async
construction, and in that callback some more state is changed, those callbacks should be wrapped in action
as well!
This is demonstrated above with the "createRandomContact-callback"
action.
If you use async
/ await
, this is a bit trickier as you cannot just wrap the async function body in action
.
In this situation runInAction
can come in handy, wrap this around the places where you intend to update the state.
(But don't make await
calls in these blocks).
Example:
@action updateDocument = async () => {
const data = await fetchDataFromUrl();
runInAction("update state after fetching data", () => {
this.data.replace(data);
this.isSaving = true;
})
}
The usage of runInAction
is: runInAction(name?, fn, scope?)
.
3. Observable Types
objects | MobX
If a plain JavaScript object is passed to observable
all properties inside that object will be made observable.
(A plain object is an object that wasn't created using a constructor function)
observable
is by default applied recursively, so if one of the encoutered values is an object or array, that value will be passed through observable
as well.
import {observable, autorun, action} from "mobx";
var person = observable({
name: "John",
age: 42,
showAge: false,
labelText: function() {
return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
},
setAge: action(function() {
this.age = 21;
})
});
autorun(() => console.log(person.labelText));
person.name = "Dave";
person.setAge(21);
Some things to keep in mind when making objects observable:
- When passing objects through
observable
, only the properties that exist at the time of making the object observable will be observable. Properties that are added to the object at a later time won't become observable, unlessextendObservable
is used. - Only plain objects will be made observable. For non-plain objects it is considered the responsibility of the constructor to initialize the observable properties.
Either use the
@observable
annotation or theextendObservable
function. - Argumentless functions will be automatically turned into views, just like
@computed
would do. For viewthis
will be automatically bound to the object it is defined on. However, if a function expression (ES6 / TypeScript) is used,this
will be bound toundefined
, so you probably want to either to refer to the object directly, or to use a classic function. - However (argumentless) functions that are declared as
action
will not be converted into computed views, but will keep their semantics. observable
is applied recursively to a whole object graph automatically. Both on instantiation and to any new values that will be assigned to observable properties in the future. Observable will not recurse into non-plain objects.- These defaults are fine in 95% of the cases, but for more fine-grained on how and which properties should be made observable, see the modifiers section.
arrays | MobX
Similar to objects, arrays can be made observable using observable
.
This works recursively as well, so all (future) values of the array will also be observable.
import {observable, autorun} from "mobx";
var todos = observable([
{ title: "Spoil tea", completed: true },
{ title: "Make coffee", completed: false }
]);
autorun(() => {
console.log("Remaining:", todos
.filter(todo => !todo.completed)
.map(todo => todo.title)
.join(", ")
);
});
todos[0].completed = false;
todos[2] = { title: 'Take a nap', completed: false };
todos.shift();
Due to limitations of native arrays in ES5 (array.observe
is only available in ES7, and arrays cannot be extend),
observable
will instrument a clone of the provided array instead of the original one.
In practice, these arrays work just as fine as native arrays and all native methods are supported, including index assignments, up-to and including the length of the array.
Bear in mind however that Array.isArray(observable([]))
will yield false
, so whenever you need to pass an observable array to an external library,
it is a good idea to create a shallow copy before passing it to other libraries or built-in functions (which is good practice anyway) by using array.slice()
.
In other words, Array.isArray(observable([]).slice())
will yield true
.
Unlike the built-in implementation of the functions sort
and reverse
, observableArray.sort and reverse will not change the array in-place, but only will return a sorted / reversed copy.
Besides all built-in functions, the following goodies are available as well on observable arrays:
intercept(interceptor)
. Can be used to intercept any change before it is applied to the array. See observe & interceptobserve(listener, fireImmediately? = false)
Listen to changes in this array. The callback will receive arguments that express an array splice or array change, conforming to ES7 proposal. It returns a disposer function to stop the listener.clear()
Remove all current entries from the array.replace(newItems)
Replaces all existing entries in the array with new ones.find(predicate: (item, index, array) => boolean, thisArg?, fromIndex?)
Basically the same as the ES7Array.find
proposal, except for the additionalfromIndex
parameter.remove(value)
Remove a single item by value from the array. Returnstrue
if the item was found and removed.peek()
Returns an array with all the values which can safely be passed to other libraries, similar toslice()
. In contrast toslice
,peek
doesn't create a defensive copy. Use this in performance critical applications if you know for sure that you use the array in a read-only manner. In performance critical sections it is recommended to use a flat observable array as well.
maps | MobX
observable(asMap(values?, modifier?))
(and map(values?, modifier?)
) creates a dynamic keyed observable map.
Observable maps are very useful if you don't want to react just to the change of a specific entry, but also to the addition or removal of entries.
Optionally takes an object or entries array with initial values.
Unlike ES6 maps, only strings are accepted as keys.
The modifier param can be one of the MobX modifiers, such as asReference
.
The following methods are exposed according to the ES6 Map spec:
has(key)
Returns whether this map has an entry the provided key. Note that the presence of a key is an observable fact in itself.set(key, value)
. Sets the givenkey
tovalue
. The provided key will be added to the map if it didn't exist yet.delete(key)
. Deletes the given key and its value from the map.get(key)
. Returns the value at the given key (orundefined
). Make sure that you guardget
calls withhas
.keys()
. Returns all keys present in this map. The insertion order is preserved.values()
. Returns all values present in this map. Insertion order is preserved.entries()
. Returns an (insertion ordered) array that for each key/value pair in the map contains an array[key, value]
.forEach(callback:(value, key, map) => void, thisArg?)
. Invokes the given callback for each key / value pair in the map.clear()
. Removes all entries from this map.size
. Returns the amount of entries in this map.
The following functions are not in the ES6 spec but are available in MobX:
toJS()
. Returns a shallow plain object representation of this map. (For a deep copy usemobx.toJS(map)
).intercept(interceptor)
. Registers an interceptor that will be triggered before any changes are applied to the map. See observe & intercept.observe(listener, fireImmediately?)
. Registers a listener that fires upon each change in this map, similarly to the events that are emitted for Object.observe. See observe & intercept for more details.merge(object | map)
. Copies all entries from the provided object into this map.
boxed values
All primitive values in JavaScript are immutable and hence per definition not observable. Usually that is fine, as MobX usually can just make the property that contains the value observable. See also observable objects. In rare cases it can be convenient to have an observable "primitive" that is not owned by an object. For these cases it is possible to create an observable box that manages such a primitive.
So observable
accepts scalar values as well and returns an object with a getter / setter function that holds this value.
Furthermore you can register a callback using its .observe
method to listen to changes on the stored value.
But in most cases it is better to use mobx.autorun
instead.
So the signature of object returned by observable(scalar)
is:
.get()
Returns the current value..set(value)
Replaces the currently stored value. Notifies all observers.intercept(interceptor)
. Can be used to intercept changes before they are applied. See observe & intercept.observe(callback: (newValue, previousValue) => void, fireImmediately = false): disposerFunction
. Registers an observer function that will fire each time the stored value is replaced. Returns a function to cancel the observer. See observe & intercept
Example:
import {observable} from "mobx";
const cityName = observable("Vienna");
console.log(cityName.get());
cityName.observe(function(newCity, oldCity) {
console.log(oldCity, "->", newCity);
});
cityName.set("Amsterdam");
objects | MobX
If a plain JavaScript object is passed to observable
all properties inside that object will be made observable.
(A plain object is an object that wasn't created using a constructor function)
observable
is by default applied recursively, so if one of the encoutered values is an object or array, that value will be passed through observable
as well.
import {observable, autorun, action} from "mobx";
var person = observable({
name: "John",
age: 42,
showAge: false,
labelText: function() {
return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
},
setAge: action(function() {
this.age = 21;
})
});
autorun(() => console.log(person.labelText));
person.name = "Dave";
person.setAge(21);
Some things to keep in mind when making objects observable:
- When passing objects through
observable
, only the properties that exist at the time of making the object observable will be observable. Properties that are added to the object at a later time won't become observable, unlessextendObservable
is used. - Only plain objects will be made observable. For non-plain objects it is considered the responsibility of the constructor to initialize the observable properties.
Either use the
@observable
annotation or theextendObservable
function. - Argumentless functions will be automatically turned into views, just like
@computed
would do. For viewthis
will be automatically bound to the object it is defined on. However, if a function expression (ES6 / TypeScript) is used,this
will be bound toundefined
, so you probably want to either to refer to the object directly, or to use a classic function. - However (argumentless) functions that are declared as
action
will not be converted into computed views, but will keep their semantics. observable
is applied recursively to a whole object graph automatically. Both on instantiation and to any new values that will be assigned to observable properties in the future. Observable will not recurse into non-plain objects.- These defaults are fine in 95% of the cases, but for more fine-grained on how and which properties should be made observable, see the modifiers section.
arrays | MobX
Similar to objects, arrays can be made observable using observable
.
This works recursively as well, so all (future) values of the array will also be observable.
import {observable, autorun} from "mobx";
var todos = observable([
{ title: "Spoil tea", completed: true },
{ title: "Make coffee", completed: false }
]);
autorun(() => {
console.log("Remaining:", todos
.filter(todo => !todo.completed)
.map(todo => todo.title)
.join(", ")
);
});
todos[0].completed = false;
todos[2] = { title: 'Take a nap', completed: false };
todos.shift();
Due to limitations of native arrays in ES5 (array.observe
is only available in ES7, and arrays cannot be extend),
observable
will instrument a clone of the provided array instead of the original one.
In practice, these arrays work just as fine as native arrays and all native methods are supported, including index assignments, up-to and including the length of the array.
Bear in mind however that Array.isArray(observable([]))
will yield false
, so whenever you need to pass an observable array to an external library,
it is a good idea to create a shallow copy before passing it to other libraries or built-in functions (which is good practice anyway) by using array.slice()
.
In other words, Array.isArray(observable([]).slice())
will yield true
.
Unlike the built-in implementation of the functions sort
and reverse
, observableArray.sort and reverse will not change the array in-place, but only will return a sorted / reversed copy.
Besides all built-in functions, the following goodies are available as well on observable arrays:
intercept(interceptor)
. Can be used to intercept any change before it is applied to the array. See observe & interceptobserve(listener, fireImmediately? = false)
Listen to changes in this array. The callback will receive arguments that express an array splice or array change, conforming to ES7 proposal. It returns a disposer function to stop the listener.clear()
Remove all current entries from the array.replace(newItems)
Replaces all existing entries in the array with new ones.find(predicate: (item, index, array) => boolean, thisArg?, fromIndex?)
Basically the same as the ES7Array.find
proposal, except for the additionalfromIndex
parameter.remove(value)
Remove a single item by value from the array. Returnstrue
if the item was found and removed.peek()
Returns an array with all the values which can safely be passed to other libraries, similar toslice()
. In contrast toslice
,peek
doesn't create a defensive copy. Use this in performance critical applications if you know for sure that you use the array in a read-only manner. In performance critical sections it is recommended to use a flat observable array as well.
maps | MobX
observable(asMap(values?, modifier?))
(and map(values?, modifier?)
) creates a dynamic keyed observable map.
Observable maps are very useful if you don't want to react just to the change of a specific entry, but also to the addition or removal of entries.
Optionally takes an object or entries array with initial values.
Unlike ES6 maps, only strings are accepted as keys.
The modifier param can be one of the MobX modifiers, such as asReference
.
The following methods are exposed according to the ES6 Map spec:
has(key)
Returns whether this map has an entry the provided key. Note that the presence of a key is an observable fact in itself.set(key, value)
. Sets the givenkey
tovalue
. The provided key will be added to the map if it didn't exist yet.delete(key)
. Deletes the given key and its value from the map.get(key)
. Returns the value at the given key (orundefined
). Make sure that you guardget
calls withhas
.keys()
. Returns all keys present in this map. The insertion order is preserved.values()
. Returns all values present in this map. Insertion order is preserved.entries()
. Returns an (insertion ordered) array that for each key/value pair in the map contains an array[key, value]
.forEach(callback:(value, key, map) => void, thisArg?)
. Invokes the given callback for each key / value pair in the map.clear()
. Removes all entries from this map.size
. Returns the amount of entries in this map.
The following functions are not in the ES6 spec but are available in MobX:
toJS()
. Returns a shallow plain object representation of this map. (For a deep copy usemobx.toJS(map)
).intercept(interceptor)
. Registers an interceptor that will be triggered before any changes are applied to the map. See observe & intercept.observe(listener, fireImmediately?)
. Registers a listener that fires upon each change in this map, similarly to the events that are emitted for Object.observe. See observe & intercept for more details.merge(object | map)
. Copies all entries from the provided object into this map.
boxed values
All primitive values in JavaScript are immutable and hence per definition not observable. Usually that is fine, as MobX usually can just make the property that contains the value observable. See also observable objects. In rare cases it can be convenient to have an observable "primitive" that is not owned by an object. For these cases it is possible to create an observable box that manages such a primitive.
So observable
accepts scalar values as well and returns an object with a getter / setter function that holds this value.
Furthermore you can register a callback using its .observe
method to listen to changes on the stored value.
But in most cases it is better to use mobx.autorun
instead.
So the signature of object returned by observable(scalar)
is:
.get()
Returns the current value..set(value)
Replaces the currently stored value. Notifies all observers.intercept(interceptor)
. Can be used to intercept changes before they are applied. See observe & intercept.observe(callback: (newValue, previousValue) => void, fireImmediately = false): disposerFunction
. Registers an observer function that will fire each time the stored value is replaced. Returns a function to cancel the observer. See observe & intercept
Example:
import {observable} from "mobx";
const cityName = observable("Vienna");
console.log(cityName.get());
cityName.observe(function(newCity, oldCity) {
console.log(oldCity, "->", newCity);
});
cityName.set("Amsterdam");
Understanding what MobX reacts to
MobX usually reacts to exactly the things you expect it to. Which means that in 90% of your use cases mobx "just works". However, at some point you will encounter a case where it might not do what you expected. At that point it is invaluable to understand how MobX determines what to react to.
MobX reacts to any an existing observable property that is read during the execution of a tracked function.
- "reading" is "dotting into" an observable property, also called dereferencing.
- "trackable functions" are the expression of
computed
, therender()
method of an observer component, and the functions that are passed as the first param towhen
,reaction
andautorun
. - "during" means that only those observables that are being read while the function is executing are tracked. It doesn't matter whether these values are used directly or indirectly by the tracked function.
In other words, MobX will not react to:
- Values that are obtained from observables, but outside a tracked function
- Observables that are read in an asynchronously invoked code block
MobX tracks property access, not values
To elaborate on the above rules with an example, suppose that you have the following observable data structure (observable
applies itself recursively by default, so all fields in this example are observable):
let message = observable({
title: "Foo",
author: {
name: "Michel"
},
likes: [
"John", "Sara"
]
})
In memory that looks as follows. The green boxes indicate observable properties. Note that the values themselves are not observable!
Now what MobX basically does is recording which arrows you use in your function. After that, it will re-run whenever one of this arrows changes; when they start to refer to something else.
Examples
Lets show that with a bunch of examples (based on the message
variable defined above):
Correct: dereference inside the tracked function
autorun(() => {
console.log(message.title)
})
message.title = "Bar"
This will react as expected, the .title
property was dereferenced by the autorun, and changed afterwards, so this change is detected.
You can verify what MobX will track by calling whyRun()
inside the tracked function. In the case of the above function it will output the following:
autorun(() => {
console.log(message.title)
whyRun()
})
WhyRun? reaction 'Autorun@1':
* Status: [running]
* This reaction will re-run if any of the following observables changes:
<EMAIL_ADDRESS>Incorrect: changing a non-observable reference
autorun(() => {
console.log(message.title)
})
message = observable({ title: "Bar" })
This will not react. message
was changed, but message
is not an observable, just a variable which refers to an observable,
but the variable (reference) itself is not observable.
Incorrect: dereference outside a tracked function
var title = message.title;
autorun(() => {
console.log(title)
})
message.title = "Bar"
This will not react. message.title
was dereferenced outside the autorun
, and just contains the value of message.title
at the moment of dereferencing (the string "Foo"
).
title
is not an observable so autorun
will never react.
Correct: dereference inside the tracked function
autorun(() => {
console.log(message.author.name)
})
message.author.name = "Sara";
message.author = { name: "John" };
This will react to both changes. Both author
and author.name
are dotted into, allowing MobX to track these references.
Incorrect: store a local reference to an observable object without tracking
const author = message.author;
autorun(() => {
console.log(author.name)
})
message.author.name = "Sara";
message.author = { name: "John" };
The first change will be picked up, message.author
and author
are the same object, and the .name
property is dereferenced in the autorun.
However the second change will not be picked up, the message.author
relation is not tracked by the autorun
. Autorun is still using the "old" author
.
Correct: access array properties in tracked function
autorun(() => {
console.log(message.likes.length);
})
message.likes.push("Jennifer");
This will react as expected. .length
counts towards a property.
Note that this will react to any change in the array.
Arrays are not tracked per index / property (like observable objects and maps) but as a whole.
Incorrect: access out-of-bounds indices in tracked function
autorun(() => {
console.log(message.likes[0]);
})
message.likes.push("Jennifer");
This will react with the above sample data, array indexers count as property access. But only if the provided index < length
.
MobX will not track not-yet-existing indices or object properties (except when using maps).
So always guard your array index based access with a .length
check.
Correct: access array functions in tracked function
autorun(() => {
console.log(message.likes.join(", "));
})
message.likes.push("Jennifer");
This will react as expected. All array functions that do not mutate the array are tracked automatically.
autorun(() => {
console.log(message.likes.join(", "));
})
message.likes[2] = "Jennifer";
This will react as expected. All array index assignments are detected, but only if index <= length
.
Incorrect: "use" an observable but without accessing any of its properties
autorun(() => {
message.likes;
})
message.likes.push("Jennifer");
This will not react. Simply because the likes
array itself is not being used by the autorun
, only the reference to the array.
So in contrast, messages.likes = ["Jennifer"]
would be picked up; that statement does not modify the array, but the likes
property itself.
Incorrect: using non-observable object properties
autorun(() => {
console.log(message.postDate)
})
message.postDate = new Date()
This will not react. MobX can only track observable properties.
Incorrect: using not yet existing observable object properties
autorun(() => {
console.log(message.postDate)
})
extendObservable(message, {
postDate: new Date()
})
This will not react. MobX will not react to observable properties that did not exist when tracking started.
If the two statements are swapped, or if any other observable causes the autorun
to re-run, the autorun
will start tracking the postDate
as well.
Correct: using not yet existing map entries
const twitterUrls = observable(asMap({
"John": "twitter.com/johnny"
}))
autorun(() => {
console.log(twitterUrls.get("Sara"))
})
twitterUrls.set("Sara", "twitter.com/horsejs")
This will react. Observable maps support observing entries that may not exist.
Note that this will initially print undefined
.
You can check for the existence of an entry first by using twitterUrls.has("Sara")
.
So for dynamically keyed collections, always use observable maps.
MobX only tracks synchronously accessed data
function upperCaseAuthorName(author) {
const baseName = author.name;
return baseName.toUpperCase();
}
autorun(() => {
console.log(upperCaseAuthorName(message.author))
})
message.author.name = "Chesterton"
This will react. Even though author.name
is not dereferenced by the thunk passed to autorun
itself,
MobX will still track the dereferencing that happens in upperCaseAuthorName
,
because it happens during the execution of the autorun.
autorun(() => {
setTimeout(
() => console.log(message.likes.join(", ")),
10
)
})
message.likes.push("Jennifer");
This will not react, during the execution of the autorun
no observables where accessed, only during the setTimeout
.
In general this is quite obvious and rarely causes issues.
The notable caveat here is passing renderable callbacks to React components, take for example the following example:
const MyComponent = observer(({ message }) =>
<SomeContainer
title = {() =>
{message.title}
}
/>
)
message.title = "Bar"
At first glance everything might seem ok here, except that the

is actually not rendered by MyComponent
(which has a tracked rendering), but by SomeContainer
.
So to make sure that the title of SomeContainer
correctly reacts to a new message.title
, SomeContainer
should be an observer
as well.
If SomeContainer
comes from an external lib, you can also fix this by wrapping the div
in it's own stateless observer
based component, and instantiating that one in the callback:
const MyComponent = observer(({ message }) =>
<SomeContainer
title = {() => <TitleRenderer message={message} />}
/>
)
const TitleRenderer = observer(({ message }) =>

{message.title}
}
)
message.title = "Bar"
Avoid caching observables in local fields
A common mistake is to store local variables that dereference observables, and then expect components to react. For example:
@observer class MyComponent extends React.component {
author;
constructor(props) {
super(props)
this.author = props.message.author;
}
render() {
return
{author.name}

}
}
This component will react to changes in the author
's name, but it won't react to changing the .author
of the message
itself! Because that dereferencing happened outside render()
,
which is the only tracked function of an observer
component.
Note that even marking the author
component field as @observable
field does not solve this; that field is still assigned only once.
This can simply be solved by doing the dereferencing inside render()
, or by introducing a computed property on the component instance:
@observer class MyComponent extends React.component {
@computed get author() {
return this.props.message.author
}
How multiple components will render
Suppose that we use the following components are used to render our above message
object.
const Message = observer(({ message }) =>

{message.title}
<Author author={ message.author } />
<Likes likes={ message.likes } />

)
const Author = observer(({ author }) =>
{author.name}
)
const Likes = observer(({ likes }) =>

{likes.map(like =>

{like}

)}

)
| change | re-rendering component |
|---|---|
message.title = "Bar" |
Message |
message.author.name = "Susan" |
Author (.author is dereferenced in Message , but didn't change)* |
message.author = { name: "Susan"} |
Message , Author |
message.likes[0] = "Michel" |
Likes |
Notes:
- * If the
Author
component was invoked like:<Author author={ message.author.name} />
. ThenMessage
would be the dereferencing component and react to changes tomessage.author.name
. Nonetheless<Author>
would rerender as well, because it receives a new value. So performance wise it is best to dereference as late as possible. - ** If likes where objects instead of strings, and if they were rendered by their own
Like
component, theLikes
component would not rerender for changes happening inside a specific like.
TL;DR
MobX reacts to any an existing observable property that is read during the execution of a tracked function.
5. Resources
Tutorials, videos and blogs
Blogs, tutorials, videos
Videos
Tutorials
Articles / blogs explaining the inner working of MobX:
Blogs
Testimonials
Want to know how people experience starting or porting projects to MobX? Besides the above listed blogs, the following threads might shed some light:
Related Projects
Examples projects
- Simple ES5 MobX examples Bite sized MobX examples all setup to run in jsFiddle.
- TodoMVC application, including Server Side Rendering: https://github.com/mobxjs/mobx-react-todomvc
- Spectacle editor: Official editor for building presentations using the Spectacle library: https://github.com/FormidableLabs/spectacle-editor
- PokemonGo webspoof Play PokemonGo on your Mac
- Contact list application (simple data fetching, routing, complex components, material UI): https://github.com/mobxjs/mobx-contacts-list
- DWatch - docker container manager, Electron, typescript, inversifyJS: https://github.com/Mercateo/dwatch
- Kratelabs interactive map / map order service https://kratelabs.addxy.com/#/ source: https://github.com/KrateLabs/KrateLabs-App
- Logpipe, a dev-logging app using MobX with Socket.IO for real-time updates
- Google Play Music Desktop Remote A React-Native app for remote controlling Google Play Music Desktop: MobX + WebSocket.
- Example with Server Side Rendering, react-router: https://github.com/kuuup/mobx-ssr-example
- Server Side Api Mocking made easy with UI: https://github.com/Raathigesh/Atmo
- Accounting System built in ASP.NET MVC, ReactJS, MobX http://www.accountgo.ph/ source
- Easy MobX example with React, MobX, Cosmic JS, shorti, https://github.com/tonyspiro/easy-mobx-example
- A simple webshop using React + mobx
- A simple webshop using JQuery + mobx.
- Simple app with Ajax, authentication, context, routing: http://stackoverflow.com/a/36164488/1983583
- Contacts MVC app with Typescript, routing etc. https://github.com/contacts-mvc/mobx-react-typescript:
- React Particles (React, MobX, D3. one app with two architectures in two branches, Flux and MVC: https://github.com/mobxjs/react-particles-experiment)
- Mortgage overpayment calculator using React with MobX: https://github.com/paulhoughton/mortgage-mobx
- Simple drag and drop application. Also provides time travelling https://github.com/mobxjs/mobx-reactive2015-demo
- The ports of the Notes and Kanban examples from the book "SurviveJS - Webpack and React" to MobX.
- Flux challenge, implemeted with MobX: https://github.com/staltz/flux-challenge/tree/master/submissions/mweststrate
- MobX + D3 codepen: http://codepen.io/timelyportfolio/pen/zrJwvE
- TypeScript + React + MobX + JSPM - Simple CRUD App example: https://github.com/piotrwitek/training-management-tool
- Github Note Taker in MobX: https://github.com/eswat2/egghead-mobx
- vcash-electron - Electron UI for the Vcash crypto currency: https://github.com/whphhg/vcash-electron
- SoundCloud client, in MobX and React: React-MobX-SoundCloud
Boilerplates
- React, Babel, Webpack: https://github.com/mweststrate/react-mobservable-boilerplate
- React, Typescript, Webpack: https://github.com/mobxjs/mobx-react-typescript-boilerplate
- MobX + React JSFiddle: https://jsfiddle.net/mweststrate/wgbe4guu/
- MobX + React JSFiddle with just ES5: https://jsfiddle.net/rubyred/55oc981v/
- generator-mobx-react Yeoman MobX, React, Webpack, Babel / Typescript project generator
- mobx-starter: Starting base for an mobx react project with optional isomorphism. MongoDB auth & sessions, hot reload, react-router
- rfx-stack RFX Stack - Universal App featuring: React + Feathers + MobX
- reaxor Boilerplate for better state management, styling, testing and cleaner code
- jspm-react lightweight React.js ES6 boilerplate with JSPM and proper hot reloading modules
- react-portal MobX, React with Flux architecture
- react-mobx-webpack Quickstart project template for learning React
- gulp-es6-sass-mobx
- react-mobx-boilerplate A small React + React Router + MobX boilerplate with a small example app.
- Small project to quickly start with deku, MobX, JSX, ES6, Babel
- React MobX Seed: Seed project using a wide set of best practices
- ReMux - MobX with flux pattern boilerplate
- react-routing-mobx-bootstrap-boilerplate Simple boilerplate with webpack, react, mobx, react router and css modules
- FutureRX The futuristic stack to create universal React applications with MobX as state manager
- mobx-isomorphic-starter Clean isomorphic starter-kit using Mobx + React + React-router + Webpack
- koa-mobx-react-starter A straightforward starter for Node javascript web projects. Using Koa, MobX, Pug and ReactJS (with universal / isomorphic server rendering)
- modular-mobx-boilerplate This is a boilerplate for developing with React + MobX. It uses a modular structure of folders for larger apps.
Frequently Asked Questions
Which browsers are supported?
MobX runs on any ES5 environment. That means that Node.js, Rhino and all browsers except for IE8 are supported. See caniuse.com
Can MobX be combined with RxJS?
Yes, see the rx-mobx interoperability package.
When to use RxJS instead of MobX?
For anything that involves explictly working with the concept of time, or when you need to reason about the historical values / events of an observable (and not just the latest), RxJs is recommended as it provides more low-level primitives. Whenever you want to react to state instead of events, MobX offers an easier and more high-level approach. In practice, combining RxJS and MobX might result in really powerful constructions. Use for example RxJS to process and throttle user events and as a result of that update the state. If the state has been made observable by MobX, it will then take care of updating the UI and other derivations accordingly.
Is React Native supported?
Yes, mobx
and mobx-react
will work on React Native. The latter through importing "mobx-react/native"
.
The devtools don't support React Native. Note that if you indend to store state in a component that you want to be able to use with hot reloading, do not use decorators (annotations) in the component, use the functions instead (eg. action(fn)
instead of @action
).
How does MobX compare to other Reactive frameworks?
See this issue for some considerations.
Is MobX a framework?
MobX is not a framework. It does not tell you how to structure your code, where to store state or how to process events. Yet it might free you from frameworks that poses all kinds of restrictions on your code in the name of performance.
Can I combine MobX with Flux?
Flux implementations that do not work on the assumption that the data in their stores is immutable should work well with MobX. However, the need for Flux is reduced when using MobX. MobX already optimizes rendering, and it works with most kinds of data, including cycles and classes. So other programming paradigms like classic MVC can now be easily applied in applications that combine ReactJS with MobX.
Can I use MobX together with framework X?
Probably. MobX is framework agnostic and can be applied in any modern JS environment. It just ships with a small function to transform ReactJS components into reactive view functions for convenience. MobX works just as well server side, and is already combined with jQuery (see this Fiddle) and Deku.
Can I record states and re-hydrate them?
Yes, see createTransformer for some examples.
Can you tell me how it works?
Sure, join the reactiflux channel or checkout the code. Or, submit an issue to motivate me to make some nice drawings :). And look at this Medium article.
Tutorials, videos and blogs
Blogs, tutorials, videos
Videos
Tutorials
Articles / blogs explaining the inner working of MobX:
Blogs
Testimonials
Want to know how people experience starting or porting projects to MobX? Besides the above listed blogs, the following threads might shed some light:
Related Projects
Examples projects
- Simple ES5 MobX examples Bite sized MobX examples all setup to run in jsFiddle.
- TodoMVC application, including Server Side Rendering: https://github.com/mobxjs/mobx-react-todomvc
- Spectacle editor: Official editor for building presentations using the Spectacle library: https://github.com/FormidableLabs/spectacle-editor
- PokemonGo webspoof Play PokemonGo on your Mac
- Contact list application (simple data fetching, routing, complex components, material UI): https://github.com/mobxjs/mobx-contacts-list
- DWatch - docker container manager, Electron, typescript, inversifyJS: https://github.com/Mercateo/dwatch
- Kratelabs interactive map / map order service https://kratelabs.addxy.com/#/ source: https://github.com/KrateLabs/KrateLabs-App
- Logpipe, a dev-logging app using MobX with Socket.IO for real-time updates
- Google Play Music Desktop Remote A React-Native app for remote controlling Google Play Music Desktop: MobX + WebSocket.
- Example with Server Side Rendering, react-router: https://github.com/kuuup/mobx-ssr-example
- Server Side Api Mocking made easy with UI: https://github.com/Raathigesh/Atmo
- Accounting System built in ASP.NET MVC, ReactJS, MobX http://www.accountgo.ph/ source
- Easy MobX example with React, MobX, Cosmic JS, shorti, https://github.com/tonyspiro/easy-mobx-example
- A simple webshop using React + mobx
- A simple webshop using JQuery + mobx.
- Simple app with Ajax, authentication, context, routing: http://stackoverflow.com/a/36164488/1983583
- Contacts MVC app with Typescript, routing etc. https://github.com/contacts-mvc/mobx-react-typescript:
- React Particles (React, MobX, D3. one app with two architectures in two branches, Flux and MVC: https://github.com/mobxjs/react-particles-experiment)
- Mortgage overpayment calculator using React with MobX: https://github.com/paulhoughton/mortgage-mobx
- Simple drag and drop application. Also provides time travelling https://github.com/mobxjs/mobx-reactive2015-demo
- The ports of the Notes and Kanban examples from the book "SurviveJS - Webpack and React" to MobX.
- Flux challenge, implemeted with MobX: https://github.com/staltz/flux-challenge/tree/master/submissions/mweststrate
- MobX + D3 codepen: http://codepen.io/timelyportfolio/pen/zrJwvE
- TypeScript + React + MobX + JSPM - Simple CRUD App example: https://github.com/piotrwitek/training-management-tool
- Github Note Taker in MobX: https://github.com/eswat2/egghead-mobx
- vcash-electron - Electron UI for the Vcash crypto currency: https://github.com/whphhg/vcash-electron
- SoundCloud client, in MobX and React: React-MobX-SoundCloud
Boilerplates
- React, Babel, Webpack: https://github.com/mweststrate/react-mobservable-boilerplate
- React, Typescript, Webpack: https://github.com/mobxjs/mobx-react-typescript-boilerplate
- MobX + React JSFiddle: https://jsfiddle.net/mweststrate/wgbe4guu/
- MobX + React JSFiddle with just ES5: https://jsfiddle.net/rubyred/55oc981v/
- generator-mobx-react Yeoman MobX, React, Webpack, Babel / Typescript project generator
- mobx-starter: Starting base for an mobx react project with optional isomorphism. MongoDB auth & sessions, hot reload, react-router
- rfx-stack RFX Stack - Universal App featuring: React + Feathers + MobX
- reaxor Boilerplate for better state management, styling, testing and cleaner code
- jspm-react lightweight React.js ES6 boilerplate with JSPM and proper hot reloading modules
- react-portal MobX, React with Flux architecture
- react-mobx-webpack Quickstart project template for learning React
- gulp-es6-sass-mobx
- react-mobx-boilerplate A small React + React Router + MobX boilerplate with a small example app.
- Small project to quickly start with deku, MobX, JSX, ES6, Babel
- React MobX Seed: Seed project using a wide set of best practices
- ReMux - MobX with flux pattern boilerplate
- react-routing-mobx-bootstrap-boilerplate Simple boilerplate with webpack, react, mobx, react router and css modules
- FutureRX The futuristic stack to create universal React applications with MobX as state manager
- mobx-isomorphic-starter Clean isomorphic starter-kit using Mobx + React + React-router + Webpack
- koa-mobx-react-starter A straightforward starter for Node javascript web projects. Using Koa, MobX, Pug and ReactJS (with universal / isomorphic server rendering)
- modular-mobx-boilerplate This is a boilerplate for developing with React + MobX. It uses a modular structure of folders for larger apps.
Frequently Asked Questions
Which browsers are supported?
MobX runs on any ES5 environment. That means that Node.js, Rhino and all browsers except for IE8 are supported. See caniuse.com
Can MobX be combined with RxJS?
Yes, see the rx-mobx interoperability package.
When to use RxJS instead of MobX?
For anything that involves explictly working with the concept of time, or when you need to reason about the historical values / events of an observable (and not just the latest), RxJs is recommended as it provides more low-level primitives. Whenever you want to react to state instead of events, MobX offers an easier and more high-level approach. In practice, combining RxJS and MobX might result in really powerful constructions. Use for example RxJS to process and throttle user events and as a result of that update the state. If the state has been made observable by MobX, it will then take care of updating the UI and other derivations accordingly.
Is React Native supported?
Yes, mobx
and mobx-react
will work on React Native. The latter through importing "mobx-react/native"
.
The devtools don't support React Native. Note that if you indend to store state in a component that you want to be able to use with hot reloading, do not use decorators (annotations) in the component, use the functions instead (eg. action(fn)
instead of @action
).
How does MobX compare to other Reactive frameworks?
See this issue for some considerations.
Is MobX a framework?
MobX is not a framework. It does not tell you how to structure your code, where to store state or how to process events. Yet it might free you from frameworks that poses all kinds of restrictions on your code in the name of performance.
Can I combine MobX with Flux?
Flux implementations that do not work on the assumption that the data in their stores is immutable should work well with MobX. However, the need for Flux is reduced when using MobX. MobX already optimizes rendering, and it works with most kinds of data, including cycles and classes. So other programming paradigms like classic MVC can now be easily applied in applications that combine ReactJS with MobX.
Can I use MobX together with framework X?
Probably. MobX is framework agnostic and can be applied in any modern JS environment. It just ships with a small function to transform ReactJS components into reactive view functions for convenience. MobX works just as well server side, and is already combined with jQuery (see this Fiddle) and Deku.
Can I record states and re-hydrate them?
Yes, see createTransformer for some examples.
Can you tell me how it works?
Sure, join the reactiflux channel or checkout the code. Or, submit an issue to motivate me to make some nice drawings :). And look at this Medium article.
6. Tips & Tricks
Common Pitfalls & Best Practices
Stuck with MobX? This section contains a list of common issues people new to MobX might run into.
Array.isArray(observable([1,2,3])) === false
In ES5 there is no way to reliably inherit from arrays, and hence observable arrays inherit from objects.
This means that regularly libraries are not able to recognize observable arrays as normal arrays (like lodash, or built-in operations like Array.concat
).
This can simply be fixed by passing calling observable.toJS()
or observable.slice()
before passing the array to another library.
As long as the external library has no intent to modify the array, this will further work completely as expected.
You can use isObservableArray(observable)
to check whether something is an observable array.
object.someNewProp = value
is not picked up
MobX observable objects do not detect or react to property assignments that weren't declared observable before.
So MobX observable objects act as records with predefined keys.
You can use extendObservable(target, props)
to introduce new observable properties to an object.
However object iterators like for .. in
or Object.keys()
won't react to this automatically.
If you need a dynamically keyed object, for example to store users by id, create observable _map_s using asMap
.
More info on asMap.
For more info see what will MobX react to?.
Use @observer
on all components that render @observable
's.
@observer
only enhances the component you are decorating, not the components used inside it.
So usually all your components should be decorated. Don't worry, this is not inefficient, in contrast, more observer
components make rendering more efficient.
Dereference values as lately as possible
MobX can do a lot, but it cannot make primitive values observable (although it can wrap them in an object see boxed observables).
So it is 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: always try to pass the owning object of an observable property.
For more info see what will MobX react to?.
Computed values run more often then expected
If a computed property is not in use by some reaction (autorun
, observer
etc), computed expressions will be evaluated lazily; each time their value is requested (so they just act as normal property).
Computed values will only track their dependencies if they are observed.
This allows MobX to automatically suspend computations that are not actively in use.
See this blog or issue #356 for an explanation.
So if you fiddle arounds, computed properties might not seem efficient. But when applied in a project that uses observer
, autorun
etc, they become very efficient.
N.B. in a next version of MobX computeds will automatically be kept alive during transactions as well, see PR: #452
Always dispose reactions
all forms of autorun
, observe
and intercept
will only be garbage collected if all objects they observe are garbage collection themselves.
So it is recommend to use the disposer function that is returned from these methods to stop them when you no longer need them.
Usually for observe
and intercept
it is not strictly necessary to dispose them if when targed this
.
For reactions like autorun
it is more tricky, as they might observe many different observables, and as long as one of them is still in scope,
the reaction will remain in scope which means that all other observables it uses are also kept alive to support future recomputions.
So make sure to always dispose your reactions when you no longer need them!
Example:
const VAT = observable(1.20)
class OrderLIne {
@observable price = 10;
@observable amount = 1;
constructor() {
this.handler = autorun(() => {
doSomethingWith(this.price * this.amount)
})
this.handler = autorun(() => {
doSomethingWith(this.price * this.amount * VAT.get())
})
this.handler()
}
}
I have a weird exception when using @observable
in a React component.
The following exception: Uncaught TypeError: Cannot assign to read only property '__mobxLazyInitializers' of object
occurs when using a react-hot-loader
that does not support decorators.
Either use extendObservable
in componentWillMount
instead of @observable
, or upgrade to react-hot-loader
"^3.0.0-beta.2"
or higher.
The display name of react components is not set
If you use export const MyComponent = observer((props =>
hi
))
, no display name will be visible in the devtools.
The following approaches can be used to fix this:
export const MyComponent = observer((props =>
hi
))
myComponent.displayName = "MyComponent"
export const MyComponent = observer(function MyComponent(props) { return
hi
 })
// 3 (transpiler will infer component name from variable name)
const _MyComponent = observer((props =>
hi
))
export const MyComponent = observer(_MyComponent)
const MyComponent = observer((props =>
hi
))
export default observer(MyComponent)
See also: http://mobxjs.github.io/mobx/best/stateless-HMR.html or #141.
The propType of an observable array is object
Observable arrays are actually objects, so they comply to propTypes.object
instead of array
.
mobx-react
will soon provide explicit propTypes for observable data structures.
Declaring propTypes might cause unnecessary renders in dev mode
See: https://github.com/mobxjs/mobx-react/issues/56
@observable
properties initialize lazily when using Babel
This issue only occurs when transpiling with Babel and not with Typescript (in which decorator support is more mature). Observable properties will not be instantiated upon an instance until the first read / write to a property (at that point they all will be initialized). This results in the following subtle bug:
class Todo {
@observable done = true
@observable title = "test"
}
const todo = new Todo()
"done" in todo
todo.hasOwnProperty("done")
Object.keys(todo)
console.log(todo.title)
"done" in todo
todo.hasOwnProperty("done")
Object.keys(todo)
In practice this is rarely an issue, only when using generic methods like Object.assign(target, todo)
or assert.deepEquals
before reading or writing any property of the object.
If you want to make sure that this issue doesn't occur, just initialize the fields in the constructor instead of at the field declaration or use extendObservable
to create the observable properties.
Defining data stores
Best Practices for building large scale maintainable projects
This section contains some best practices we discovered at Mendix while working with MobX. This section is opinionated and you are in no way forced to apply these practices. There are many ways of working with MobX and React, and this is just one of them.
Stores
Let's start with stores. In the next sections we will discuss actions and React components as well. Stores can be found in any Flux architecture and can be compared a bit with controllers in the MVC pattern. The main responsibility of stores is to move logic and state out of your components into a standalone testable unit that can be used in both frontend and backend JavaScript.
Stores for the user interface state
Most applications benefit from having at least two stores. One for the UI state and one or more for the domain state. The advantage of separating those two is you can reuse and test domain state universally, and you might very well reuse it in other applications. The ui-state-store however is often very specific for your application. But usually very simple as well. This store typically doesn't have much logic in it, but will store a plethora of loosely coupled pieces of information about the UI. This is ideal as most applications will change the UI state often during the development process.
Things you will typically find in UI stores:
- Session information
- Information about how far your application has loaded
- Information that will not be stored in the backend
- Information that affects the UI globally
- Window dimensions
- Accessibility information
- Current language
- Currently active theme
- User interface state as soon as it effects multiple, further unrelated components:
- Current selection
- Visibility of toolbars, etc.
- State of a wizard
- State of a global overlay
It might very well be that these pieces of information start as internal state of a specific component (for example the visibility of a toolbar). But after a while you discover that you need this information somewhere else in your application. Instead of pushing state in such a case upwards in the component tree, like you would do in plain React apps, you just move that state to the ui-state-store.
Make sure this state is a singleton. For isomorphic applications you might also want to provide a stub implementation of this store with sane defaults so that all components render as expected. You might distribute the ui-state-store through your application by passing it as a property through your component tree. You can also pass this store by using context or make it globally available as a module. For testing purposes, I recommend to just pass it through the component tree.
Example of a store (using ES6 syntax):
import {observable, computed, asStructure} from 'mobx';
import jquery from 'jquery';
class UiState {
@observable language = "en_US";
@observable pendingRequestCount = 0;
@observable windowDimensions = asStructure({
width: jquery(window).width(),
height: jquery(window).height()
});
constructor() {
jquery.resize(() => {
this.windowDimensions = getWindowDimensions();
});
}
@computed get appIsInSync() {
return this.pendingRequestCount === 0
}
}
singleton = new UiState();
export default singleton;
Domain Stores
Your application will contain one or multiple domain stores. These stores store the data your application is all about. Todo items, users, books, movies, orders, you name it. Your application will most probably have at least one domain store.