React and MobX — Lessons Learned
Get started with MobX as your state management library today
Introduction
A few years ago, I had the challenge to assemble and develop the front end of a product. When I started, I was told that I was supposed to use React, but besides that, I could build however I wanted.
Redux?
Among many other choices I had to make, I had to decide if I would use Redux or not, as well as how I would deal with the data in the application. I was already familiar with Redux and some Flux implementations, as well as their ecosystems, but one thing that has bothered me a little bit was that there are more and more libraries and dependencies for each type of problem/situation you come across — and, as most of the knowledge available online is based on the most famous boilerplates, you end up being forced to plug several libs into the project without even knowing if you really need them.
MobX
Then I began looking for alternatives and found MobX. At first, I thought it was very different, so there is some kind of a learning curve, but it’ll pass quickly. It is interesting how MobX has good support from developers/companies, how it’s not driven by corporate interests at first, and how the community is relatively big (even though it has nothing on Redux, of course). After all, it is very easy to start using MobX. However, I fell into some traps that I would have liked to know better when I started, so I decided to write some tips.
Before explaining how MobX works, as I’m talking about React, it’s worth saying briefly that I also had to choose whether to use the Create React App or not. I needed server-side rendering, which CRA doesn’t support, and among Facebook’s own recommendations for SSR, there’s Next.js and Razzle. I really like Next and the whole Zeit ecosystem, but one way or another, you end up getting stuck in it. On the other hand, Razzle is very flexible by definition. It is just a basis for the project and pretty much everything in it is customizable. As I didn’t know then much about what was ahead in my challenge, I chose it (worth a look). I just needed to install and import mobx and mobx-react into the project in order to use React (via Razzle) with MobX.
How It Works
Let’s take a look at some basic MobX concepts.
The focus of the library is scalable state management (like Redux, not necessarily with React) of applications — and this scalability is achieved by transparent reactivity (the fancy name is TFRP, transparent reactive functional programming). This means that everything related to the state of the application must be derived automatically (or automagically). It will get clearer soon.
Observables
What allows that to happen is the use of observables. Quite simply, an observable adds to an existing data structure the possibility of being “observed” by someone. It is similar to the pattern design of Pub/Sub or Mediator, where part A asks to be notified when something happens in part B, but here, in addition to all of this happening automatically (without the necessity of “subscribing”), what is observed is the value in itself, rather than callbacks created by you.
The use of decorators in MobX is optional: It is just a way to write a little less code and maintain the current structure. Every decorator has a corresponding function. To enable decorators, you may need to change the Babel settings.
An example of that is the use of decorators @observable
and @observer
:
Please note that without having to write anything specific, the observer will react alone when the observable name changes its value. Even though you have a lot of complex observables, MobX internally only records what is being used by you in the render method.
Cool, right? Very easy and straightforward.
Previous example without the decorator:
You can find more examples of proceedings with or without the decorator in the documentation.
Computed Values
At first it may seem a little unnecessary to use this feature, but it’s quite powerful. Although it is very easy to create an observable and an observer, MobX offers several tools for you to keep the distinction between the data layer and the interface layer very clear. Computed values are values derived from one or more observables.
You can use this for many things, for example assembling an object for an AJAX request:
Here I used an observable.map
.
Rather than putting pieces together in components or other modules, ask yourself if you can do this with computed values. If so, use it!
Custom Reactions
Not only are React components able to react to observables, but any code can. There are three functions for this: autorun
, reaction
, and when
.
Consider the previous example. If you really want to make an AJAX call whenever you update a search filter:
OK! That’s it, right? It’s enough at least for you to play a little and to have your application working.
Actions
Although it is not mandatory, I strongly recommend using actions. By default, MobX will do its job and every observer will react to the observables they are using (only these) when they are changed. However, nothing impedes observables from being changed from anywhere, including from their own component.
Using actions is completely optional, but in addition to helping the performance, it helps you to structure your code.
OK! Now we can go to the tips. Just to recap:
- For changeable data used in one or more components — as well as for data that cause any Javascript code to run frequently — use an observable.
- If the data you need is a combination of other data, use computed values.
- For React components to react to observables, use
observer
. - For any JavaScript code to react to observables, use
autorun
,reaction
, orwhen
. - To better organize your code and maintain a standard of what can modify observables, use actions.
First Tip: Use Domain Stores
Separate your observables and the logic related to them in Stores by domain (for example user, search, feed, etc.).
To use Stores in your components, use a Provider
and inject
.
Second Tip: Use enforceActions
Using the enforceActions
option, MobX will only observe changes in observables that happen as a result of a declared action.
Third Tip: Just Expose Your Actions
Choose a way to only allow access to the actions of your Stores and to the reading of the observables, either by exporting them to another object or making it clear that they are public. Some actions will be internal to the Store and must not be used by components — and, on the other hand, others must. There are many ways to do this, and in the MobX documentation, we are told that the library does not have a say about how you want to handle events and what triggers actions. You can use some Flux implementations or any other library to create this communication channel. Once a user action happens and triggers an internal action from your store, MobX takes care of the rest reactively.
Conclusion
Both MobX and Redux are good and deliver what they promise. Although many benchmarks say that in some use cases MobX outperforms Redux, I have no way to deny or confirm which one is better. For the moment, they just seem to be different styles. I believe that the ideal situation is: If you are going to use Redux, know the reason for each library to exist and try to use as few of them as possible, making the code as direct as possible. If using MobX, separate data and logic from the interface, and make sure to follow a simple and redundant pattern.
If you want to know more, the following two links are enough:
These are good examples of projects with MobX:
- https://github.com/lionsharecapital/lionshare-desktop
- https://github.com/fcsonline/react-transmission
- https://github.com/gothinkster/react-mobx-realworld-example-app
And of course, not everything is Mobx, nor React. The important thing is to know how to use the tools when it suits.