Understanding Mobx — behind the scenes
If you are into front end web development and working on ReactJs, good chances that you are using Redux as your state management library. As most of us are. Redux is a 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 add-ons available.
But it's 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 Reactive-JS. Here are a few links to get started quickly.
Introduction to Mobx
Mobx works on Observable pattern where your app state is observable (subject) and your presentation components are observers (subscribers). Observer component would re-render whenever the observable state is mutated. That's it! No reducers, action creators, middlewares boiler code. The app looks really simple. More on the mobx concepts here.
A simple observable state
To understand how mobx works 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.
Create an observable state
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 an RxJS observable function and create a new observable object. Whenever we set a value on firstName the observe.next()
is called which would rerender the observer component. How? Let's see.
Observer component
Now we just need to bind our component to observable state such that whenever the setter function is called our component would rerender. Let's see an example in React.
On input change `person.firstName = e.target.value;
---> observe.next()
---> () => setState({})
---> rerender <App/>
So, now that we know how an observable state would work with a react component, let's create a mobx app.
Mobx State
Mobx provides 2 ways to create state and components
- Wrapper functions like
Observable({...state })
- Decorators like
@observable const state = {}
.
In this example, we will use functions as annotations are still not part of official ES and you will need to configure babel to use them.
We created a primitive, nested object and array state objects. We need to pay special attention to how we create the 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.
Observer(<App/>)
creates an observer component. We directly import our state and use it in the app. Mobx will keep a watch on all observable state that we use in this app and rerender our component when state mutates.
A 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(). Other state properties can be assessed directly.
- An observable state is a mobx object wrapping our actual state values. Notice that we use toJS to get the actual object
toJS(state.indiaScores).join(", ")
This needs to be done before access the state values for something like sending as API params.
Now we define the actions that will mutate our state
A couple of points to note when mutating a 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 by mobx. - When mutating a property on a nested object we need to mind the level which we are changing. For example.
state.score.india.kohli++; // triggers rerender
but
let score = state.score.india.kohli;
score += 1;
state.score.india = { kohli: score }; // Does not trigger rerender
Reason being the observable 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 Observable state example earlier.
I hope this was helpful and easy to understand. This example is available on codesandbox if you want to play around.
Thanks for reading.