The Drawbacks of MobX
The pros and cons of MobX
Join the DZone community and get the full member experience.
Join For FreeI would like to talk about what I could rarely find in articles covering MobX, about those drawbacks that ruin the impression of use, as well as how to crack this problem. I also need to describe its pros to justify my own choice. So, let’s start.
MobX is a state manager, which now has a new version 6 that works due to Proxy. The further opinion is based on using MobX v6 together with the React library when developing mobile (React Native) and web applications. It is worth clarifying that I used MobX v4, react-easy-state, Redux, Zustand in previous projects, and I’m also familiar with a dozen alternative state managers at the level of reading their documentation. I would also like to note that all the pros and cons listed below aren’t complete and can be detected if compared to other state managers.
Pros
With the release of version 6, classes and decorators are no longer needed. I see both of them as unnecessary and even harmful syntactic sugar. So, I believe the ability to create object repositories in the new version is an excellent choice.
Example:
import { makeAutoObservable } from 'mobx';
export const blogStore = makeAutoObservable({
blogs: {},
posts: {},
createBlog: () => {},
createPost: () => {},
});
It is quite natural to use repositories as objects. From here follow type hints, references to fields, auto-import, and other goodies – Proxy works wonders.
Example:
import { blogStore } from 'stores';
const Blog = () => {
const posts = chatStore.posts;
const onPress = chatStore.createPost;
return null;
};
You can easily change states. Yes, I’m all for immutability, but this is where I don’t see any reason for this. The fact is that, if needed, I can create full snapshots of all repositories by putting them in a single object and calling JSON.stringify or something custom. In Redux, the problem is solved by including "immer" for deeply nested objects. And yes, it all depends on how precise the object change is, and we must use functional programming tools wherever it is possible.
Example:
import { makeAutoObservable } from 'mobx';
export const blogStore = makeAutoObservable({
posts: {},
createPost: (post) => {
blogStore.posts[post.id] = post;
},
});
Selectors. This is where MobX looks amazing. When we need data slices only based on repositories, we use getters. In other cases, we store parameterized selectors in separate files using computedFn from MobX-utils. At the same time, they all are automatically memoized by MobX, which allows for good application performance. Not that Redux has any problems with reselect, but here it is easier to write and read code.
Example:
import { makeAutoObservable } from 'mobx';
import { accountStore } from 'stores/accountStore';
export const blogStore = makeAutoObservable({
posts: {},
get myPosts() {
return Object.values(posts).filter((post) => post.userId === accountStore.userId);
}
});
Cons
Wrapping components with observer HOC to make them more reactive. Just like any state managers that aren’t based on hooks or selectors, this requires writing snippets for components and takes some getting used to. Besides, this involves memoizing components. But since all of our components are already memoized, we’re just replacing one call with another.
Example:
import { observer } from 'mobx-react-lite';
const Blog = () => null;
export default observer(Blog);
Components fail when their code, containing hooks, is updated on the fly. More specifically, adding or removing a hook with subsequent saving leads to failure. It’s about the poor performance of Fast Refresh with a HOC wrapped around the exported default component, rather than MobX. So as not to change the usual project structure in which we use export default observer (Component) to export components by default, I had to study writing Babel plugins. I created a plugin that wrapped the observer call into the component function declaration and removed the call from the export. That was fine. Of course, you might ask why I would do that. After all, according to the MobX documentation, the component needs to be wrapped just when it is declared. My answer would be that when using HOCs in the export, the code looks more beautiful and has less nesting. Besides, changing memo to observer is easier when you need to use MobX stores.
Example:
import { observer } from 'mobx-react-lite';
const Blog = () => {
// removing or adding a hook will cause fast refresh to fail
const [visible, setVisible] = useState(false);
return null;
};
export default observer(Blog);
You need to be careful when working with MobX objects inside a component. Since we’re working with reactive mutable data, it is no longer possible to hope for their immutability when transferred to somewhere else, including inside other objects, unlike when using Redux data. For example, if we want to keep the same change history in some editor. In this case, use a MobX function called toJS, which converts data into regular JavaScript objects.
Example:
import { blogStore } from 'stores';
import { useRef } from 'react';
import { toJS } from 'mobx';
const Blog = () => {
const posts = blogStore.posts;
const prevMessages = useRef();
const onPress = () => {
const postsPurified = toJS(posts);
prevPosts.current = postsPurified;
// not to call toJS before that = shoot yourself in the foot
};
return null;
};
There is a lack of good tools similar to Redux DevTools. Luckily, I had some developments for react-easy-state, which allowed me to supplement them and create a library so that MobX could work with Redux DevTools. In short, I use it to wrap all MobX actions and replace them with logging functions, as well as create snapshots of repositories when they are called. Monitoring changes to MobX stores is now easy and enjoyable.
And of course, I cannot but mention how inconvenient it is to debug Proxy objects, which are the main part of MobX 6. You always have to open them to click on the target field, where the object we need is located. When displaying logs, you can use wrapping in toJS from MobX, but I still don’t have any solution for debugging. Perhaps there is a setting for displaying Proxy in the browser and Visual Studio Code, I don’t know yet.
Conclusion
MobX is a worthy state manager, although it required some work to meet the needs of our project. Despite some problems when debugging Proxy objects, it helps write code easily and provides excellent code readability. In my opinion, its good performance due to memoized getters and computedFn from MobX-utils makes it one of the best solutions.
Opinions expressed by DZone contributors are their own.
Comments