I recently began using React with MobX at my new company. Having used Vue with VueX at my previous job, I immediately noticed congruence between the two patterns. What I had become so fond of with Vue after being a long time React/Redux user, I found baked into the MobX philosophy.

I’ve seen a lot of people adopting Vue this year. Sarah Drasner’s fantastic series on CSSTricks probably played a big part in that migration. However I believe we JS developers live in a meritocracy (for the most part) and I mainly credit this diaspora to a few things:

  • event handling
  • multiple rendering options (templates or render functions with JSX)
  • truly reactive components

This last point is the kicker. Vue has reactivity baked directly into its components. I think this simplicity and thoughtful architecture is the main reason for all the hype and it’s why I took such a liking to Vue. This is not a novel concept, as we’ve seen native reactivity in frameworks like Ember and Knockout before, but its elegance in Vue makes it superior.

This piece is not meant to be a comparison of React + MobX and Vue + VueX. This post from the Vue docs that topped HackerNews last week does a fair job at this. I earnestly believe both solutions are great and have their use cases. This this post aims to do is introduce MobX concepts from the perspective of someone who loves Vue reactivity.

Enter MobX

When I first learned that I was would be moving to Vue professionally, I privately grieved. There were many things I initially missed: a large developer community, dedicated support from a tech powerhouse in Facebook; however, the thought of going back to my days of Redux boilerplates and a more verbose state management solution was less than enticing.

In fact, I came to appreciate thinking in “the Vue way” so much so that I expressed that same sadness when I learned that I would be moving back to React. That is until I learned that my new team was using MobX.

The state management pattern that MobX brings to a view framework like React might seem so similar to Vue that at first you might ask yourself…

Evan You tweet on MobX

Although humorous (and somewhat true), I see this as a mostly straw man argument. MobX does not claim to supplant Vue and its ideology. What it does offer is a less-opinionated and more robust alternative to the reactive data management pattern built into Vue.

Travel Log

In this section, I want to demonstrate how we might write some code in Vue, followed by its implementation with React + MobX. As a basis for our comparison, let’s use a tiny app I built that lists several places around the world and let’s us mark the locations of the ones we’ve visited.

An app like this requires the use of state to manage our currently selected location and the list of places we’ve visited. I’ve implemented the app three different ways: once using React local state, once more in Vue, and one last time with React + MobX. We’ll go through the general paradigms of local state management using the logic contained in these apps.

data => observable

State is essential to any complex application. While React provides an option for managing local state, it requires usage of the setState function and is limited to a single object. Vue introduces data, instead, as a mechanism for declaring reactive local variables.

In our app, we use data to keep track of the cities we’ve visited, our currently selected city, and a few variables we need to manage the alert.

Just like that, this data (i.e. your state) is now reactive. You can update these variables from any JS within the Vue component and Vue will re-render the component if necessary. This removes the need to use setState — or any other function for that matter — to update local state.

MobX takes a similar approach, but instead of a singular data object your reactive variables are wrapped in observables.

By wrapping your component in an observer from the mobx-react connector library, the component becomes reactive to mutations of these variables. Like Vue, the component will now update when they are used in the render function of the component.

computed => computed

The addition of computed variables begins to shine light on the advantage of this approach. When only using local state, we are left to compute certain variables every time the component renders. Computed variables allows us to compute a new value every time a dependent variable changes, caching that value until one of the dependencies changes.

In our app, Vue computes an alphabetically sorted version of the cities array. This is preferred over computing it every time we render, as this array does not actually change throughout the lifecycle of our component. As these transformations become more expensive, this feature becomes crucial for building a performant PWA.

MobX gives us the same ability with its computed object.

The only difference here is syntax. All the spoils remain the same.

method => action

One major difference between Vue and React is the makeup of the components themselves. Vue eschews ES6 classes in favor of custom components with specific options (i.e. data, computed, etc). This gives it tight control over where the user can control certain behavior and certainly brings a sense of organization to each component. One place where this is evident are the method functions. These are the only functions capable of updating local state that you can explicitly invoke from within your template (for the sake of this discussion, that is).

Nothing too crazy here. We select a city and update the selectedCityId accordingly. We can, of course, write this logic inline in our click handlers, but methods help us organize our code and keep it DRY.

Lo and behold, the MobX equivalent is not too far off with actions.

There really isn’t much of a difference here at the base level. In fact, you don’t even need to use actions to update local observables. You can simply update your observables in regular functions, as long as strict mode is not enabled. Where actions become beneficial is in their inherent performance boost, described in the docs as such:

…actions will batch mutations and only notify computed values and reactions after the (outer most) action has finished. This makes sure intermediate or incomplete values produced during an action are not visible to the rest of the application until the action has finished.

With the addition of runInAction, this makes writing complex state-updating functions easier by simplifying the predictability of your component’s state.

watch => reaction

The final piece to any complex component’s state management suite is its need for imperative code. In our app, for example, we want to display an alert banner every time the user updates their log. When using only React’s local state, we are forced to explicitly tell the banner to appear with the showAlert function. Acting strictly as a view layer, React’s goal is not to emit side effects, so it only makes sense that local state does not provide us with optimal ways to produce them. However, any front end developer knows that these are unavoidable when building a robust UI. If only there was a way for us to react to specific state updates…

Thinking

As you may have expected, Vue gives us the handy concept of watch to do just that.

This piece of code essentially says that every time our visited object changes, we run a function in response. I love this pattern because it no longer requires us to think about when we need to display our alert. Instead, our code just knows that every time we update our log, the banner should display. We are even given a deep option for instructing Vue to watch for mutations deep within our state objects, meaning immutability is not even required for this functionality.

Not surprisingly, MobX offers almost identical functionality with reactions.

Admittedly, it’s not as clear what is going on here at first glance, so let’s break it down.

A reaction takes two functions as arguments. The first instructs what state variables to watch, while the second contains the code to run when one of those values mutates.

While Vue offers simplicity when it comes to imperative code, MobX rebuttals with power. Reactions are capable of listening to multiple observables at once and only reacting a single time to one mutation, something I always found difficult to work around with watch. MobX also offers a suite of tools that give you more fine-grained control for running imperative code. autorun will simply run when any of its dependencies change, while when replaces the first function with a predicate, only running the latter if the former returns true. This can all be achieved in some fashion with reaction, but these helper functions work to make your state…dare I say it…predictable.

The Verdict

It’s an unequivocal tie!

In all seriousness, both of these patterns are great and provide similar mechanisms for performing the same goals. This piece was simply meant to enlighten developers on the parallels between both libraries. I really do like to use them both in different situations.

However, I would be remiss if I didn’t provide a short (possibly subjective) list of where I think each approach stands out:

MobX Wins

  1. ES7 & Beyond: MobX takes full advantage of recent additions to the language, most blatantly classes and decorators. These are newer concepts to JS, but they provide tried and true patterns that have existed in other languages like Python for eons. Best of all, they are not required, so you can easily use MobX without them.
  2. Predictability: Vue does a great job at this, but I think MobX wins out here. MobX builds out an insanely complex dependency graph behind the scenes that runs all your reactive code synchronously. This is crucial for debugging and actually understanding what the hell is going on in your code!
  3. Extensibility: MobX just works in its own right, so you can use it in just about any context you’d like. Vue’s clean syntax comes at the cost of tightly-coupling your source code with a particular framework. So far I’ve only used it with React projects, but it can just as easily be used in Preact, Inferno, and other rising stars.

Vue Wins

  1. Modifiers: In addition to the basic actions, Vue provides event modifiers and other niceties for writing clean code. It’s a clear indicator to the thought that went in to making this an approachable framework for developers and it speaks to the relevancy of directives.
  2. Documentation: Vue definitely excels here. The documentation is succinct, well-organized, and in-depth in the right areas. Again, this shines a light on the delightful developer experience Vue seeks to provide.
  3. Organization: This can go either way, but as someone obsessed with linting and prettier, Vue brings a very understandable pattern to your development workflow. Everything is where it needs to be, making code handoffs and reviews a more seamless process for a collaborative team.
  4. Clean DOM: For all the benefits of decorators, inject and observer (from mobx-react) do muddy up the DOM with extraneous wrapper components. This becomes frustrating during debugging using the React dev tools and looking for your actual component among the redwood of a tree. Vue keeps the DOM nice and tidy by only rendering your custom components.

Conclusion

My hope for this post was that you got a basic understanding of the similarities between MobX and Vue for managing local state. Both libraries have done a fantastic job of identifying pain points experience by UI developers in recent years and building pragmatic tools to address them. These paradigms can solve many higher-order problems, as well: managing global state with VueX and MobX, handling streams, and proxies.

Feel free to reach out with any questions/comments via the issues of the vue-mobx repo I made containing this post’s code. Go forth and explore the possibilities!

Additional Resources

The following two tabs change content below.
Jake Peyser

Jake Peyser

Jake is a software engineer with a passion for product development and user experience. His background in computer science gives him a technical depth that allows him to understand complex software architecture, while his client and product experience provide empathy and an appreciation for the importance of the customer journey.
Jake Peyser

Latest posts by Jake Peyser (see all)