Move from Redux to MobX - reduce boilerplate (2024)

Mike Borozdin

Posted on

Move from Redux to MobX - reduce boilerplate (2) Move from Redux to MobX - reduce boilerplate (3) Move from Redux to MobX - reduce boilerplate (4) Move from Redux to MobX - reduce boilerplate (5) Move from Redux to MobX - reduce boilerplate (6)

#react #tutorial #javascript #webdev

Originally posted on mikeborozdin.com

MobX is a statement management library. Unlike Redux it doesn’t require a lot of boilerplate code. In this post we’ll talk how you can benefit from using MobX vs Redux.

There’s a video version of this article that I originally recorded for the React Native London. If you prefer to read text, just scroll below.

Redux is great for extracting application state and business logic out of components. However, you end up with a lot of boilerplate. Your code will be scattered around many different place. Think of a typical user action - usually, you have to write an action definition, an action creator, and finally a reducer. Now, imagine you have a bug in that action - you’ll have to trace it in at least two different places - an action creator and a reducer.

This tweet by Ben Lesh - a member of the RxJS core team - perfectly summarises that.

Move from Redux to MobX - reduce boilerplate (7)

Ben Lesh

@benlesh

Move from Redux to MobX - reduce boilerplate (8)

Redux: You know EXACTLY where your state is.

...You just don't know where the code that manages your state is.

00:41 AM - 22 Mar 2020

Move from Redux to MobX - reduce boilerplate (9) Move from Redux to MobX - reduce boilerplate (10) Move from Redux to MobX - reduce boilerplate (11)

MobX allows you to manage your state in a far more concise way. It's a fairly simple library that you can get started with in almost no time. It's got more than 400k+ weekly downloads on NPM. And many companies, including mine, use it in production.

Unlike, Redux, it's not afraid to mutate state. In fact, it's based on the observer pattern which is all about mutations and reactions to them.

Instead of doing a theoretical introduction of MobX, I'll use an example. We'll build a simple application first with Redux and then'll we'll move it to Mobx, while gradually explaining its concepts.

The sample app is a classis todo app:

  • You can see a list of todo items
  • You can add new ones
  • And all of that will be done via the API calls
    • That's to make comparison between Redux and MobX more interesting
    • After all, in real world we get and save data via APIs most of the time

Move from Redux to MobX - reduce boilerplate (12)

First of all, the Redux app needs action creators.

There'll be two action creators:

  • addTodo()
  • getTodos()

Since we need to send API requests, there'll be a bit of complexity - we'll have to return a function an async function from the action creators.

store/action-creators.js

import { GET_TODOS } from './constants';export const addTodo = (todo) => { return async (dispatch) => { await fetch('http://localhost:9999/todos', { method: 'post', body: todo }); dispatch(getTodos()); };};export const getTodos = () => { return async (dispatch) => { const res = await fetch('http://localhost:9999/todos'); const { todos } = await res.json(); dispatch({ type: GET_TODOS, todos }); };};

Then we need to add reducers that will set the initial state and modify it once the actions are dispatched.

store/reducers.js

import { ADD_TODO, GET_TODOS } from './constants';const initialState = { todos: []};const todos = (state = initialState, action) => { switch (action.type) { case ADD_TODO: { return { ...state, todos: [...state.todos, action.todo] }; } case GET_TODOS: { return { ...state, todos: action.todos }; } default: return state; }};

We need to throw a few constants in the mix, so that the reducers module doesn't depend on the action creator one and vice versa.

store/constants.js

export default todos;export const ADD_TODO = 'ADD_TODO';export const GET_TODOS = 'GET_TODOS';

Finally, we need to wire it app together and call createStore().

store/store.jsx

import { applyMiddleware, createStore } from 'redux';import thunkMiddleware from 'redux-thunk';import todos from './reducers';export default createStore(todos, applyMiddleware(thunkMiddleware));

Redux store so far

It feels like we had to write a lot of code for such a small application, doesn't it?

Redux wiring

As the final step we have to inject the store into the application context:

index.jsx

import React from 'react';import ReactDOM from 'react-dom';import { Provider } from 'react-redux';import store from './store/store';import App from './App';ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root'));

Components

What about the components. We left them till the end, but they are not particularly complicated:

Todos/Todos.jsx

import React, { useEffect } from 'react';import { connect } from 'react-redux';import { getTodos } from '../store/action-creators';import './Todo.css';const Todos = ({ todos, getTodos }) => { useEffect(() => { getTodos() }, [getTodos]); return ( <div className='list'> {todos.map((todo, index) => ( <div key={index} className='todo'>{todo}</div> ))} </div> );};const mapStateToProps = (state) => ({ todos: state.todos});const mapDispatchToProps = (dispatch) => ({ getTodos: () => { dispatch(getTodos()) }});export default connect(mapStateToProps, mapDispatchToProps)(Todos);

Todos/Todos.jsx

import React, { useState } from 'react';import { connect } from 'react-redux';import { addTodo } from '../store/action-creators';import './NewTodo.css';const NewTodo = ({ addTodo }) => { const [todo, setTodo] = useState(''); return ( <div> <input type='text' onChange={e => setTodo(e.target.value)} placeholder='New todo item...' className='new-todo' /> <button onClick={() => addTodo(todo)} className='new-todo-button'> Add a new todo </button> </div> );};const mapDispatchToProps = (dispatch) => ({ addTodo: (todo) => dispatch(addTodo(todo))});export default connect(null, mapDispatchToProps)(NewTodo);

Now, remember a very verbose Redux store we wrote? Let's see how we re-write it in MobX.

import { observable, action } from 'mobx';export default class TodoStore { @observable todos = [] @action async addTodo(todo) { await fetch('http://localhost:9999/todos', { method: 'post', body: todo }); this.getTodos(); } @action async getTodos() { const res = await fetch('http://localhost:9999/todos'); const { todos } = await res.json(); this.todos = todos; }}

And that's it! Those mere 25 lines of code replace Redux's action creators, reducers, and the other bits!

Now, we have a very concise store that both has an application state and business logic, yet doesn't mix them together. Indeed, MobX stores are a great answer to the question - 'Where do I put my business logic and HTTP calls in React?'. Also, MobX stores are extremely easy to unit test.

Okay, but how is it possible? Let's dive into the code.

MobX observables

First of all, we declare an array that will hold todo items and mark it as an observable:

@observabletodos = []

What does the @observable annotation mean? It means that all the changes to the array will be monitored and all the observers will be notified? What are the observers? Usually, they are React components that reference observables. And they are re-rendered if corresponding observables change. We'll have a look at it below.

Now, having declared the data, we need to declare operations that can be performed on it. And, in our case, there are two:

  • Adding a new item
  • Getting todos

And you can see that they are declared as class methods and have the @action annotation:

store/store.js

@actionasync addTodo(todo) { await fetch('http://localhost:9999/todos', { method: 'post', body: todo }); this.getTodos();}@actionasync getTodos() { const res = await fetch('http://localhost:9999/todos'); const { todos } = await res.json(); this.todos = todos;}

Both addTodo() and getTodos() are just regular functions that make HTTP calls and update some data. The only two special things are:

  • They have the @action annotation
  • The data they modify - this.todos is marked as @observable.

Why does the methods need to be annotated with @action?

First of all, it's a nice convention that clearly marks methods that modify observable data. Secondly, MobX does performance optimisation if observable data is mutated in an action. Finally, MobX has a strict mode that would throw an exception if observables are modified outside of the actions.

Finally, you need to change the root of your application to this:

index.jsx

import React from 'react';import ReactDOM from 'react-dom';import { Provider } from 'mobx-react';import TodoStore from './store/store';import App from './App';ReactDOM.render( <React.StrictMode> <Provider todoStore={new TodoStore()}> <App /> </Provider> </React.StrictMode>, document.getElementById('root'));

It's almost exactly the same as the one for Redux. The only difference is that we import Provider from a different module.

Okay, we have re-written the store in MobX. It does look much more concise than the one in Redux. But what about the components? Will they need much re-write?

Luckily, no! Let's examine the Todos component that is now MobX enabled:

Todos/Todos.jsx

import React, { useEffect } from 'react';import { observer, inject } from 'mobx-react'import './Todo.css';const Todos = ({ todoStore }) => { useEffect(() => { todoStore.getTodos() }, [todoStore]); return ( <div className='list'> {todoStore.todos.map((todo, index) => ( <div key={index} className='todo'>{todo}</div> ))} </div> );};export default inject(({ todoStore }) => ({ todoStore }))(observer(Todos));

As you can see the component stayed largely unchanged. Similarly, to the Redux version it receives a property, but this time the property contains a MobX store that have a list of todos. It doesn't need need the mapStateToProps(). Instead, of connect() we have inject() that, as the name suggests, injects the data store into the component.

The most crucial thing that the component is wrapped inside the observer() function. As mentioned before, components wrapped inside observer() will be re-rendered once observable change.

Will all observer components re-render if any observable changes?

No! MobX is smart enough only to trigger re-rendering of the components read observables that get changed. For example, if you have a component that reads from the observable called todos, but it the the @observable employees that gets changed, then your component will not be re-rendered.

What about components that modify data?

Easy!

NewTodo/NewTodo.jsx

import React, { useState } from 'react';import { inject } from 'mobx-react';import './NewTodo.css';const NewTodo = ({ todoStore }) => { const [todo, setTodo] = useState(''); return ( <div> <input type='text' onChange={e => setTodo(e.target.value)} placeholder='New todo item...' className='new-todo' /> <button onClick={() => todoStore.addTodo(todo)} className='new-todo-button'> Add a new todo </button> </div> );};export default inject(({ todoStore }) => ({ todoStore }))(NewTodo);

Once again, it's very similar to its Redux version. And unlike the Todos component we don't need to wrap it inside observer. Indeed, NewTodo doesn't need to be rendered when todos change. We just need to inject the store with inject().

The source code of both the Redux and the MobX version is available on Github. It also includes the API server. So you can all run it.

  • MobX is a great and mature solution for state management of React applications
  • You'll have almost zero boilerplate in comparison to Redux
  • MobX stores are great place for business logic and HTTP requests
  • Give it a try
  • Have questions? There might be a few answers below
  • What about hooks?
    • The example above shows that MobX works nicely with React hooks such as useEffect() and useState()
  • But React Redux also has useSelector() and useDispatch()?
    • So does MobX React have useObserver() and useStores() that you can use instead of observer() and inject().
    • Personally, I prefer the HoCs - observer() and inject() because they make it easier to unit test components. But that could be a matter of taste.
  • Can you have more than one store?
    • Easily! You can have as many stores as you want.
    • I recommend having a store per feature
    • We have around 15 stores on the product I'm working on
  • Does it come with debug tools?
    • MobX comes with a great trace module
    • Plus, you can use the standard React devtools to understand why components got re-rendered
  • Do you have to use ES decorators?
    • No. Each ES decorator has a corresponding function which allows to wrap your variables/class properties and components
  • Does MobX work with any kind of component?
    • You can mark 'fat' and simple functional components as observer
    • But you cannot do that with PureComponents
Move from Redux to MobX - reduce boilerplate (2024)

FAQs

How do I migrate from Redux to MobX? ›

To migrate from Redux to MobX, begin by creating MobX stores to replace the Redux store. Define observables for pieces of state and actions that modify these observables. Replace useSelector and useDispatch with MobX's observer higher-order component or hooks for React components.

Why is MobX not more popular? ›

MobX was a popular state management library for React that allowed you to create observable and reactive data models. However, with the introduction of React Hooks, MobX became less appealing and, sometimes, conflicted with React's philosophy.

Why MobX is better than Redux? ›

Redux stands out for its structured nature and predictability, making it ideal for complex projects. In contrast, MobX offers a concise syntax and simplified reactivity, making it better for applications seeking simplicity.

What is MobX store? ›

→ MobX is a state management tool. This is another store like Redux. → The code is less complicated yet powerful.

Is MobX faster than Redux? ›

In terms of performance, redux is less efficient than Mobx. Redux process action, after dispatching action updates the UI. In complex circ*mstances, it could be a performance concern sometimes. Redux may not be efficient when we compare it with other libraries, but it avoids unnecessary rendering.

Which is best MobX or Redux? ›

Redux provides performance optimizations through the use of immutable data and a strict update cycle. MobX, on the other hand, uses fine-grained reactivity, which can lead to better performance in certain scenarios.

Which companies use MobX? ›

MobX is one of the most popular Redux alternatives and is used (along with MobX-State-Tree) by companies all over the world, including Netflix, Grow, IBM, DAZN, Baidu, and more. It supports a full set of features for a modern state management system -- all in a package with zero dependencies other than MobX itself.

Why should I use MobX? ›

Actions are all the things that alter the state. MobX will make sure that all changes to the application state caused by your actions are automatically processed by all derivations and reactions. Synchronously and glitch-free.

Is Zustand better than MobX? ›

MobX and Zustand are both state management libraries for JavaScript applications. Let's explore the key differences between the two. API Complexity: MobX provides a more advanced and complex API compared to Zustand.

Is Redux still recommended? ›

In most instances, the answer is: No! You are no longer obligated to turn to Redux as the default method for handling state in your React applications. Instead, there are alternative options worth considering.

What is the best state management for React? ›

Which state management is best in React? React's useState is the best option for local state management. If you need a global state solution, the most popular ones are Redux, MobX, and the built-in Context API. Your choice will depend on the size of your project, your needs, and your engineers' expertise.

Should I use MobX in React? ›

MobX is a state management library. It can be used with any framework but offers helper methods to use with React using react-mobx package. MobX should be used when complex state management is required if you want to avoid prop drilling and in cases where you require a global shared state among multiple components.

What is the difference between Redux store and MobX store? ›

Redux uses plain JavaScript objects as data structures to store the state. While using Redux, updates must be tracked manually. This can be harder in applications that have a huge state to maintain. Contrasting Redux, MobX uses observable data, which helps automatically track changes through implicit subscriptions.

What is state management MobX vs Redux? ›

MobX offers simplicity and much-needed flexibility to React developers, whereas React Redux is strict and follows a predictable architecture. Understanding such contrasting nuances of React Redux vs MobX is important for making an informed decision for effectively handling state management in your React app.

What is the difference between MobX and Redux saga? ›

MobX utilizes observable data structures to automatically track and propagate changes, ensuring that components are always up to date. On the other hand, redux-saga employs the concept of sagas, which are generator functions that enable complex asynchronous actions to be handled in a clear and manageable way.

How do I progressively migrate to Redux Toolkit? ›

The general approach to migrating Redux logic is:
  1. Replace the existing manual Redux store setup with Redux Toolkit's configureStore.
  2. Pick an existing slice reducer and its associated actions. ...
  3. As needed, replace existing data fetching logic with RTK Query or createAsyncThunk.
Mar 20, 2024

How do I get a state from Redux? ›

Use store. getState() to retrieve the state from the store , and assign this to a new variable currentState .

References

Top Articles
Latest Posts
Article information

Author: Pres. Carey Rath

Last Updated:

Views: 6328

Rating: 4 / 5 (61 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Pres. Carey Rath

Birthday: 1997-03-06

Address: 14955 Ledner Trail, East Rodrickfort, NE 85127-8369

Phone: +18682428114917

Job: National Technology Representative

Hobby: Sand art, Drama, Web surfing, Cycling, Brazilian jiu-jitsu, Leather crafting, Creative writing

Introduction: My name is Pres. Carey Rath, I am a faithful, funny, vast, joyous, lively, brave, glamorous person who loves writing and wants to share my knowledge and understanding with you.