Redux or MobX: What I learned after refactoring a medium-sized React app
State containers are a must for medium to large-sized React applications. The basic idea is to keep the state abstracted from your components and manage it somewhere else. Using a state container like Redux or MobX allows you to share a global centralised state across all your components.
This article is not a tutorial per se but a list of takeaways, pros and cons of each library and the impact on an existing React application.
Redux is a state container manager loosely based on the Flux architecture. In Redux you have actions creators and reducers. Actions creators are the first step towards updating the state, for example: “saveSettings”, o “insertRow”. Each action creator will call a reducer, which receives data and updates the state. To wrap up, actions is where your logic should be (ajax calls, computations, etc), and reducers are very simple pure functions that update the state. Once the state is updated, the view will be rendered by React.
The beauty of this (and any other state container) is that your state is global, which you can then inject to any of your components, even deep descendants.
- Solid conventions: if you are working with a large team in which strict rules are beneficial, then Redux is the way to go. Having to separate your state updates between actions and reducers, forces developers to keep a very clean testable code.
- Your state is immutable: or so they say. If you go by definition, you cannot really have an immutable state. Your state must change. What Redux does though, it creates a copy of the current state, applies the changes, and return a new object. This is strictly enforced by Redux but could also be applied to any state container.
- Time travel: as per the item before, having an immutable state allows you to easily perform actions like undo, redo, and time travel, because you can keep copies of your past state changes like snapshots of time.
- It is light: it’s just about 2k, as pretty much all the heavy lifting will be done by your own actions and reducers.
- Pretty much backed by Facebook: Dan Abramov, the co-author of Redux, works at Facebook, so you can rest assured that this library has partial official support from the React team.
- Dev tools: because it has been around for longer, Redux has a good set of development tools for testing and monitoring state changes, including but not limited to hot-reloading.
- It takes a while to learn and implement: Redux is not very intuitive if you come from an object-oriented sort of way of looking at things. The way the data flows might be confusing at first but will eventually make sense. It took me about 10 hours to refactor an app from pure React to Redux, excluding the time I spent going through the docs and tutorials.
- Lots of code: when people say Redux has a lot of boilerplate, they are right. You can reduce it to a certain extent but be prepared to have some code that may not look DRY at first. This same problem usually applies to any Flux implementation.
- No batching: this is actually common across most state containers and you may miss it. Remember that React’s setState() uses batching to update the state. If you have multiple setState()s one after the other, React will be smart enough to wait for the last one before triggering a re-render. This is good sometimes but may introduce some side effects. With Redux though, the state is updated immediately, which may lead to some unnecessary renders, which in consequence leads to more code and conditions inside your componentWillReceiveProps().
- Thunks: because the state is updated immediately after every action, if you want to wait for an ajax call to complete before updating the state, then you are in trouble. Redux won’t do this by default, which means, you need an extra library to make it work. These are called thunks in Redux, which allow action creators to return functions. I found this to be quite cumbersome, which means that for any relatively basic app, thunks are a must.
- Refs are a pain to manage: since all your actions have to be moved to outside the components, it means that any refs you define within your components are useless. Your actions won’t be able to see your refs, which can leave you stranded. A solution to this is to make your actions return promises, but now you need to install a separate library. I have an open question on StackOverflow about this if you have come across this conundrum before.
MobX is also a state container manager like Redux. However, MobX doesn’t enforce any convention on you. You can use Flux if you want, or just reactive coding. It’s really up to you and your team. MobX also introduces new concepts to the arena, like observables, observers and actions. Observables are basically the properties of your state object. Observers are your React components that “monitor” the state changes (they watch for changes in the observables) and finally, Actions, are the ones that trigger a change in the observables. Very straightforward. MobX is a “reactive” state container.
If you want to learn more about reactive programming, you must have a look at this article in CSS-Tricks.
- It just makes sense: at least for most of us coming from object-oriented programming. You don’t need to understand a new data flow to get started with MobX, just make your state observable, make your components observers, and your functions actions. That’s it. The learning curve is very low and you can grasp the concepts in minutes. It took me about 3 hours to rewrite an app from pure React to MobX, including the time it took me to digest the docs!
- Less refactoring and boilerplate: you don’t have to write actions nor reducers if you don’t want to, which means that your existing React application can be moved to MobX in no time. Nothing is mandatory in MobX. Your code can stay pretty much the way it is.
- Open to any architecture: you can use the Flux architecture with MobX if you want, or any other pattern that you prefer. No convention is enforced on you. This could also lead to inconsistencies in large teams though, so it’s up to you to write the rules that your project should follow.
- No thunks or middlewares: because actions work differently in MobX, you don’t need to install additional libraries to use RESTful APIs. Simply make your ajax calls and the state will be updated whenever the callback is triggered or your promise is done.
- Your stores can be anything: In Redux, your stores are plain objects, and the recommendation is that they should be as simple as possible. In MobX your state can be a class if you want. This opens the door to very complex implementations in which your state could also have methods within it.
- Open to interpretation: if you have a large team working with MobX and you don’t enforce an architecture, this may lead to everyone doing things differently.
- The isArray(ObservableArray) pitfall: when you make an array “observable”, it will be wrapped in an ObservableArray object. So, if you want to pass that array to another component that validates for pure array inputs, you will get an error. For this reason, when you make arrays observables, you have to make sure to “purify” them by slicing them. You can read more about it in the common pitfalls and best practices article.
What has been said in several articles is still valid after all. If you plan to have your app exposed to a large team, say 10 or more developers, you should probably go with Redux. This will enforce an out-of-the-box coding convention for your team. However, be advised that Redux has a higher learning curve and your team must be comfortable using it for it to be effective. On the other hand, if you code is to be maintained by just you, or a handful or proficient coders, MobX is the answer. You will most certainly end up with cleaner code and get to do some actual coding very quickly. MobX however, can scale very well if you apply the Flux methodology to your existing code.
I personally, had more fun working with MobX than I did with React. The fact that I had to install another library to work with a RESTful API really put me off and on top of that, having my refs being rendered useless was the end of it. The level of overengineering I had to do to make it work was just too much to be justifiable.
As always, it all comes down to your personal preferences, team size and app size. There is a tool for everyone, it’s just a matter of using them in the correct environment.