Why we are using MobX with Redux
React itself provides two tools for it and one of them is Context API which works well with apps where state updates are rarely performed. The most common use would be a global theme switcher — light or dark theme. However, with complex state logic and frequent updates, it can easily end with re-rendering every component even if nothing changes for part of them. Also, it was an experimental API for a long time and that fact forced many developers to look for 3rd party implementations. The other tool is a hook called useState whose most common use case is a local component state. Hooks are a relatively new React feature and are only used in functional components.
The clear winner is Redux (56.5k Github stars vs MobX — 24.1k Github stars) but that doesn’t mean there aren’t bad implementations of it in many projects, for example, developers putting everything in app-wide Redux store which should be just a local component state.
Redux has one big plus — data immutability (your application data is unchangeable by any other source except Redux). This improves debugging and makes your app more predictable. But it still has a lot of boilerplate to create before reaping any benefits of using it. Any new team member will have to go through many files to understand what is going on let alone try to optimise it for a smooth UI experience.
It is certainly possible to write performant apps with React-Redux but it is even easier to write slow applications. Often there will be unnecessary renders called, unoptimised selectors and big data structures which can start to become quite heavy on performance when you have UI with hundreds of elements.
Trying out MobX
We at Evolution use Redux extensively throughout our web-based games both for common game logic-like settings and for each game separately e.g. game state. Each new game comes with many UI challenges to solve and the audience expects fancier games with cooler animations.
A few years ago there was an internal discussion to try out MobX for a new game where development was still in the early stage. For us, one of the main benefits was that you could write less code to achieve the same thing.
Code Comparison — MobX vs Redux
I created two sandboxes and some Github gists to showcase how much code is needed for state management for each of them.
17 lines of code
67 lines of code — not counting the component map state to props and map dispatch to props.
Full code and simple performance demo:
Create a sandbox where a button when pressed would add balance (+ 100 dollars) to the current balance 10, 000 times and print the time it took in milliseconds.
Without any optimisations the results are pretty much similar — average 1 second for both MobX and Redux.
More Complex Example
The test involved bingo cards (5 x 5) and drawn balls with numbers that were updating the cards. Storybook was used to prototype the test and 2 stories with different components and state management were created.
Test results showed that the same performance could be achieved.
The difference was small when comparing with optimised Redux implementation, thus the decision was made to go with MobX and try it out.
At the start, everything worked well and smoothly but the design changed and changed until we had to allow players to be able to play with 200 cards at the same time and later even 400 cards.
We noticed that game doesn’t perform well anymore — animations became janky and on some older devices, the application froze for some seconds with too many parts in UI moving at the same time.
In our game the cards are re-ordered, rescaled, including winning line animations, card glows, and circles being drawn on won numbers and those are not the only moving components in UI. All that is happening at the same time.
If you are not paying attention it is easy to make unoptimised code with too many reactions from state changes mixed with computed properties and observing all the data. Similar to how you can easily write sluggish Redux applications it is just as easy as MobX if you aren’t testing performance during development.
… and how we solved them
After some analysis, we found ways to improve the performance. Turns out you can tell MobX to selectively react to specific reference changes in the object. By default, MobX will use deep observability which means that if an encountered value is an array or object the observability will be applied recursively.
Another improvement is to make sure that you unsubscribe from each reaction, observer, auto-run, or intercept to not cause memory leaks or unwanted behavior.
This was not the only thing we used to improve performance but in the end, we are happy with the results.
MobX is known for its “auto-magic” — observe, detect and propagate changes to where they are being used without writing a lot of code.
It is a good alternative and is production-ready, many of our games use it already.
However, it is not a magic wand to just make every Redux minus disappear.
Be mindful of your implementation and shortcomings of both Redux and MobX!
The article was written and produced by Andris Vilde.