Key Takeaways
- Both Redux and MobX are open-source libraries that provide client-side state management, support time-travel debugging, and have extensive support for React/React Native frameworks. However, they differ in their core philosophies and approaches.
- MobX is easy to learn and use, requires less code to write, fully supports object-oriented programming, and makes dealing with nested data easy. However, it provides too much freedom which can lead to unmaintainable code, it’s hard to debug, and there could be a better alternative to it in the future.
- Redux is more popular and well-suited for building large and complex projects due to its strict guidelines on state code writing, making it easy to write tests and develop maintainable code. However, it requires more boilerplate code and is not well-suited for small projects.
- The code comparison between Redux and MobX shows that MobX’s code base is leaner and can help build applications faster. However, it’s very easy to write poor, unmaintainable code with MobX.
- The decision on whether to use Redux or MobX depends on the type of project you’re working on, and the resources available to you.
For a lot of JavaScript developers, the biggest complaint with Redux is the amount of boilerplate code needed to implement features. A better alternative is MobX which provides similar functionality but with lesser code to write.
For MobX newbies, take a quick look at this introduction written by MobX’s creator. You can also work through this tutorial to gain some practical experience.
The goal of this article is to help JavaScript developers decide which of these two state management solutions are best for their projects. I’ve migrated this CRUD Redux project to MobX to use as an example in this article. I’ll first discuss the pros and cons of using MobX, and then I’ll demonstrate actual code samples from both versions to show the difference.
The code for the projects mentioned in this article can be found on GitHub:
If you enjoy this post, you might also like to sign up for SitePoint Premium and watch our course on working with forms using React and Redux.
What Do Redux and MobX Have in Common?
First, let’s look at what they both have in common. They:
- are open-source libraries
- provide client-side state management
- support time-travel debugging via the redux-devtools-extension
- are not tied to a specific framework
- have extensive support for React/React Native frameworks.
4 Reasons to Use MobX
Let’s now look at the main differences between Redux and MobX.
1. Easy to learn and use
For a beginner, you can learn how to use MobX in just 30 minutes. Once you learn the basics, that’s it. You don’t need to learn anything new. With Redux, the basics are easy too. However, once you start building more complex applications, you’ll have to deal with:
- handling async actions with redux-thunk
- simplifying your code with redux-saga
- defining selectors to handle computed values, etc.
With MobX, all these situations are “magically” taken care of. You don’t need additional libraries to handle such situations.
2. Less code to write
To implement a feature in Redux, you need to update at least four artifacts. This includes writing code for reducers, actions, containers and components. This is particularly annoying if you’re working on a small project. MobX only requires you to update at least two artifacts (i.e. the store and the view component).
3. Full support for object-oriented programming
If you prefer writing object-oriented code, you’ll be pleased to know you can use OOP to implement state management logic with MobX. Through the use of decorators such as @observable
and @observer
, you can easily make your plain JavaScript components and stores reactive. If you prefer functional programming, no problem — that’s supported as well. Redux, on the other hand, is heavily geared towards functional programming principles. However, you can use the redux-connect-decorator library if you want a class-based approach.
4. Dealing with nested data is easy
In most JavaScript applications, you’ll find yourself working with relational or nested data. To be able to use it in a Redux store, you’ll have to normalize it first. Next, you have to write some more code to manage tracking of references in normalized data.
In MobX, it’s recommended to store your data in a denormalized form. MobX can keep track of the relations for you, and will automatically re-render changes. By using domain objects to store your data, you can refer directly to other domain objects defined in other stores. In addition, you can use (@)computed decorators and modifiers for observables to easily solve complex data challenges.
3 Reasons Not to Use MobX
1. Too much freedom
Redux is a framework that provides strict guidelines on how you write state code. This means you can easily write tests and develop maintainable code. MobX is a library and has no rules on how to implement it. The danger with this is that it’s very easy to take shortcuts and apply quick fixes that can lead to unmaintainable code.
2. Hard to debug
MobX’s internal code “magically” handles a lot of logic to make your application reactive. There’s an invisible area where your data passes between the store and your component, which makes it difficult to debug when you have a problem. If you change state directly in components, without using @actions
, you’ll have a hard time pinpointing the source of a bug.
3. There could be a better alternative to MobX
In software development, new emerging trends appear all the time. Within a few short years, current software techniques can quickly loose momentum. At the moment, there are several solutions competing with both Redux and Mobx. A few examples are Relay/Apollo & GraphQL, Alt.js and Jumpsuit. Any of these technologies has the potential to become the most popular. If you really want to know which one is best for you, you’ll have to try them all.
Code Comparison: Redux vs MobX
Enough theory, let’s look at the code. First, we compare how each version does bootstrapping.
Bootstrapping
Redux Version:
In Redux, we first define our store and then we pass it to App
via Provider
. We’ll also need to define redux-thunk
and redux-promise-middleware
to handle asynchronous functions. The redux-devtools-extension
allows us to debug our store in time-traveling mode.
// src/store.js
import { applyMiddleware, createStore } from "redux";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from "./reducers";
const middleware = composeWithDevTools(applyMiddleware(promise(), thunk));
export default createStore(rootReducer, middleware);
-------------------------------------------------------------------------------
// src/index.js
…
ReactDOM.render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>,
document.getElementById('root')
);
MobX Version:
In MobX, we need to setup multiple stores. In this case, I’m using only one store, which I’ve placed in a collection named allStores
. A Provider
is then used to pass the stores collection to the App
.
As mentioned earlier, MobX doesn’t need external libraries to handle async actions, hence the fewer lines. However, we do need the mobx-remotedev
to connect to the redux-devtools-extension
debugging tool.
// src/stores/index.js
import remotedev from 'mobx-remotedev';
import Store from './store';
const contactConfig = {
name:'Contact Store',
global: true,
onlyActions:true,
filters: {
whitelist: /fetch|update|create|Event|entity|entities|handleErrors/
}
};
const contactStore = new Store('api/contacts');
const allStores = {
contactStore: remotedev(contactStore, contactConfig)
};
export default allStores;
-------------------------------------------------------------------------------
// src/index.js
…
ReactDOM.render(
<BrowserRouter>
<Provider stores={allStores}>
<App />
</Provider>
</BrowserRouter>,
document.getElementById('root')
);
The amount of code here is roughly about the same in both versions. MobX has fewer import statements though.
Props injection
Redux Version:
In Redux, state and actions are passed to props using react-redux’s connect()
function.
// src/pages/contact-form-page.js
…
// accessing props
<ContactForm
contact={this.props.contact}
loading={this.props.loading}
onSubmit={this.submit}
/>
…
// function for injecting state into props
function mapStateToProps(state) {
return {
contact: state.contactStore.contact,
errors: state.contactStore.errors
}
}
// injecting both state and actions into props
export default connect(mapStateToProps, { newContact,
saveContact,
fetchContact,
updateContact
})(ContactFormPage);
MobX Version:
In MobX, we simply inject the stores
collection. We use @inject
at the top of a container or component class to do this. This makes stores
available in props
, which in turn allows us to access a specific store and pass it to a child component. Both state and actions are accessed via properties in the store
object hence no need to pass them separately as with the case in Redux.
// src/pages/contact-form-page.js
…
@inject("stores") @observer // injecting store into props
class ContactFormPage extends Component {
…
// accessing store via props
const { contactStore:store } = this.props.stores;
return (
<ContactForm
store={store}
form={this.form}
contact={store.entity}
/>
)
…
}
The MobX version seems to be easier to read. However, we can use redux-connect-decorators to simplify Redux code. In that case, there’ll be no clear winner.
Defining stores, actions, and reducers
To keep this article lean, I’ll show you a code sample for just one action.
Redux Version: In Redux, we need to define actions and reducers.
// src/actions/contact-actions.js
…
export function fetchContacts(){
return dispatch => {
dispatch({
type: 'FETCH_CONTACTS',
payload: client.get(url)
})
}
}
…
// src/reducers/contact-reducer
…
switch (action.type) {
case 'FETCH_CONTACTS_FULFILLED': {
return {
...state,
contacts: action.payload.data.data || action.payload.data,
loading: false,
errors: {}
}
}
case 'FETCH_CONTACTS_PENDING': {
return {
...state,
loading: true,
errors: {}
}
}
case 'FETCH_CONTACTS_REJECTED': {
return {
...state,
loading: false,
errors: { global: action.payload.message }
}
}
}
…
MobX Version:
In MobX, the logic for the action and the reducer is done in one class. I’ve defined an async action that calls another action entities fetched
after response
has been received.
Since MobX uses the OOP style, the Store
class defined here has been refactored to allow easy creation of multiple stores using the class constructor. Hence the code demonstrated here is base code that’s not tied to a particular domain store.
// src/stores/store.js
…
@action
fetchAll = async() => {
this.loading = true;
this.errors = {};
try {
const response = await this.service.find({})
runInAction('entities fetched', () => {
this.entities = response.data;
this.loading = false;
});
} catch(err) {
this.handleErrors(err);
}
}
…
Believe it or not, the logic defined in both versions do the same tasks, which are:
- update the UI loading state
- fetch data asynchronously
- catch exceptions and update state.
In Redux, we’ve used 33 lines of code. In MobX, we’ve used about 14 lines of code to achieve the same result! A major benefit of the MobX version is that you can reuse the base code in almost all the domain store classes with little or no modification. That means you can build your application faster.
Other differences
To create forms in Redux, I’ve used redux-form. In MobX, I’ve used mobx-react-form. Both libraries are mature and help you handle form logic easily. Personally, I prefer mobx-react-form
, since it allows you to validate fields via plugins. With redux-form
, you either write your own validation code or you can import a validation package to handle validation for you.
One tiny downside with MobX is that you can’t directly access certain functions in observable objects since they are not really plain JavaScript objects. Luckily, they have provided the function toJS()
which you can use to convert observable objects to plain JavaScript objects.
Conclusion
Clearly, you can see that MobX’s code base is far much leaner. Using OOP style and good development practices, you can rapidly build applications. The major downside is that it’s very easy to write poor, unmaintainable code.
Redux, on the other hand, is more popular and well suited for building large and complex projects. It’s a strict framework with safeguards ensuring every developer writes code that’s easy to test and maintain. However, it’s not well suited to small projects.
Despite MobX’s drawbacks, you can still build large projects if you follow good practices. In the words of Albert Einstein, “Make everything simple as possible, but not simpler”.
I hope I’ve provided enough information to make a clear case whether to migrate to MobX or stick with Redux. Ultimately, the decision depends on the type of project you’re working on, and the resources available to you.
This article was peer reviewed by Dominic Myers and Vildan Softic. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
If you’re looking to up your Redux game, sign up for SitePoint Premium and enroll in our course Redux Design Issues and Testing. In this course, you’ll build a Redux application that receives tweets, organized by topic, through a websocket connection. To give you a taster of what’s in store, check out the free lesson below.
Frequently Asked Questions (FAQs) on Redux vs MobX
What are the key differences between Redux and MobX?
Redux and MobX are both state management libraries, but they differ in their core philosophies and approaches. Redux follows a strict and predictable state management pattern based on the Flux architecture. It has a single store, and state changes are made through actions and reducers. On the other hand, MobX adopts a more flexible and intuitive approach. It allows multiple stores and state changes are made directly through actions.
Is Redux or MobX better for large-scale applications?
Redux is often favored for large-scale applications due to its predictable and transparent state management. The strict pattern of actions and reducers makes it easier to track state changes, which can be crucial in complex applications. However, MobX, with its more flexible approach, can also be used effectively in large-scale applications, especially when developers prefer a less boilerplate and more straightforward coding style.
How does the learning curve compare between Redux and MobX?
Redux has a steeper learning curve compared to MobX. It requires understanding the concepts of actions, reducers, and the store, and how they interact with each other. On the other hand, MobX is generally considered easier to grasp as it uses more familiar programming concepts like observables and actions, and it requires less boilerplate code.
How does Redux handle asynchronous actions compared to MobX?
Redux requires middleware like Redux-Thunk or Redux-Saga to handle asynchronous actions. These middleware allow actions to dispatch other actions, or to delay the dispatch of an action. MobX, on the other hand, can handle asynchronous actions directly without the need for additional middleware.
Can Redux and MobX be used together in a single application?
Yes, Redux and MobX can be used together in a single application. However, this is not commonly done as it can lead to unnecessary complexity. It’s generally recommended to choose one or the other based on the specific needs and constraints of your project.
How does testing compare between Redux and MobX?
Redux has a clear advantage when it comes to testing. Its predictable state changes and pure functions (reducers) make it easy to test. MobX, while not as straightforward to test due to its more dynamic nature, can still be effectively tested using tools like Jest.
How does performance compare between Redux and MobX?
Both Redux and MobX have good performance characteristics and can handle large state trees efficiently. However, MobX can have an edge in certain scenarios due to its fine-grained observability system, which updates only the components that are directly affected by a state change.
How does community support and ecosystem compare between Redux and MobX?
Redux has a larger community and a more mature ecosystem compared to MobX. There are more resources available for learning Redux, and more third-party libraries designed to work with it. However, MobX has been gaining popularity and its community is growing.
What are some use cases where MobX might be a better choice than Redux?
MobX might be a better choice for projects where developers prefer a more straightforward and less boilerplate coding style, or when the project requires fine-grained control over state updates. It’s also a good choice when the team is more comfortable with object-oriented programming concepts, as MobX leverages these heavily.
What are some use cases where Redux might be a better choice than MobX?
Redux might be a better choice for large-scale applications where predictability and transparency of state changes are crucial. It’s also a good choice when the team is comfortable with functional programming concepts, as Redux leverages these heavily. Additionally, Redux’s mature ecosystem and large community can be a deciding factor.
I write clean, readable and modular code. I love learning new technologies that bring efficiencies and increased productivity to my workflow.