If you are into front end web development and working on ReactJs, I bet you are using Redux as your state management library as most of us are. Redux is widely popular lib for React applications and why should it not be. It is simple, scalable and solves all your sync and async problems with a wide variety of tools and addons available.
But its not the only one out there. There are a bunch of alternatives that might work for you. One of them is Mobx. I am writing this to discuss my understanding of this library and how it works.
Before we proceed you would need to familiarise with some concepts of Observable pattern and ReactiveJS. Here are a few links to get started quickly.
Observable Pattern,
RxJS.
Mobx works on Observable pattern where your app state is observable (subject) and your presentation components are observers (subscribers). Observer component would rerender whenever the observable state is mutated. Thats it! No reducers, action creators, middlewares boilercode. The app look really simple. More on the mobx concepts here.
To understand how mobx reacts and what it reacts to, we will create a small observable state of our own and make our observer component react to its changes. This will help in understanding how mobx works behind the scenes. We would use RxJS to create our state and a React component.
https://gist.github.com/33f249c176cb6a7fdb4e0e0d8a60fafe
In the above code snippet person
is our state. We add a new property firstName
to it and define get()
and set()
methods. We wrap this in a RxJS observable function and craete a new observable object. Whenever we set value on firstName the observe.next()
is called which would rerender the observer component. How? Let's see.
Now we just need to bind our component to observable state such that whenever the setter function is called our component would rerender. Lets see an example in React. https://gist.github.com/d562b65e2018e85a2d54b603cce5cdb2
The flow is like person.firstName = e.target.value;
--->observe.next()
---> () => setState({})
---> rerender <App/>
So, now that we know how an observable state would work, lets create a mobx app.
Mobx provides 2 ways to create state and components
- Observable()
- @observable annotations. In this example we will use functions as annotations are still not part of official ES. https://gist.github.com/e974f7f761bccd25e2f2987f33fdb3ee
We created premitive, nested object and array state objects. We need to pay special attention on how we create state and how we mutate it. Because mobx will not react to mutations if this is not done correctly. We discuss this further in the article.
https://gist.github.com/947d5cd8a748400efec71e1d8509eddf
Observer(<App/>)
creates a observer component. We directly import our state and use it in App. Mobx will keep a watch on all observable state that we use in this app and rerender our component when state mutates.
Couple of points to note:
- We import observer from
react-mobx
a react specific lib from mobx. - We need to call get() method on primitive values created using observable.box() .
- The observable state is a mobx object wrapping our actuall state values. Notice that we use toJS to get the actual object
toJS(state.indiaScores).join(", ")
Now we define the actions that will mutate our state
https://gist.github.com/3acbf6be8705fc2c754840ccb6424f89
A couple of points to note when mutating an mobx state.
- Your action should be wrapped in
action()
or use@action
annotations. Mobx also provides other apis like runInAction() for inline actions. - For primitive properties created using
observable.box()
we need to use set() and get() methods on the property. These are exposed by the observable state created bu mobx. - When mutating a property on an nested object we need to be mind the level which we are changing. e.g.
state.score.india.kohli++; // triggers rerender
Reason being the observalbe component is observing property 'kohli'. When the setters on kohli property are called, only then the observer will be notified of the change triggering a rerender. This is clear from our Observalble state example earlier.let score = state.score.india.kohli; score += 1; state.score.india = { kohli: score }; // Does not trigger rerender