mobx, redux, and vuex are libraries designed to manage application state in complex frontend applications, but they follow fundamentally different philosophies. redux enforces a strict unidirectional data flow with immutable state and pure reducers, making state changes predictable and debuggable. mobx leverages observables and proxies to allow mutable state that automatically triggers updates, offering a more flexible and less boilerplate-heavy approach. vuex is the official state management pattern for Vue.js, integrating deeply with Vue's reactivity system to provide a centralized store with mutations, actions, and getters. While redux is framework-agnostic, vuex is tightly coupled with Vue, and mobx can be used with React, Vue, or vanilla JavaScript.
When building complex frontend applications, managing state consistently is one of the hardest challenges. mobx, redux, and vuex solve this problem in very different ways. redux focuses on predictability and immutability, mobx emphasizes simplicity and reactivity through proxies, and vuex provides a structured store specifically designed for Vue's lifecycle. Let's break down how they handle state, updates, and integration.
redux requires you to dispatch actions that are processed by pure reducer functions. You never modify state directly. With modern Redux, we use Redux Toolkit to simplify this.
// redux (with Redux Toolkit)
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
// Toolkit allows "mutating" logic here via Immer
state.value += 1;
}
}
});
// Dispatching
store.dispatch(counterSlice.actions.increment());
mobx allows you to modify state directly on observable objects. Changes automatically trigger re-renders in observed components.
// mobx
import { makeAutoObservable } from 'mobx';
class Counter {
value = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
// Direct mutation is allowed and tracked
this.value += 1;
}
}
// Usage
const counter = new Counter();
counter.increment();
vuex requires explicit mutations to change state. actions are used for async logic and commit mutations.
// vuex
import { createStore } from 'vuex';
const store = createStore({
state: () => ({ count: 0 }),
mutations: {
increment(state) {
state.count++;
}
},
actions: {
increment(context) {
context.commit('increment');
}
}
});
// Dispatching
store.dispatch('increment');
redux historically had high boilerplate, but Redux Toolkit reduces this significantly. You still need to configure a store and provide it to your app.
// redux setup
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
const store = configureStore({ reducer: counterSlice.reducer });
// In App
<Provider store={store}><App /></Provider>
mobx requires minimal setup. You often just create observable classes or objects and use provider components if needed for React.
// mobx setup
import { Provider } from 'mobx-react';
const stores = { counter: new Counter() };
// In App
<Provider {...stores}><App /></Provider>
vuex is built into Vue's plugin system. You create a store instance and pass it to the Vue app root.
// vuex setup
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
createApp(App).use(store).mount('#app');
redux excels here. Every change is an action with a payload, making it easy to log, replay, or time-travel debug using Redux DevTools.
// redux action log
// { type: 'counter/increment', payload: undefined }
// Clear history of exactly what happened and when.
mobx tracks dependencies automatically, which is great for performance but can make it harder to trace where a specific change originated without strict mode enabled.
// mobx
// Changes happen silently via proxy traps.
// Enforce strict mode to catch unauthorized mutations:
import { configure } from 'mobx';
configure({ enforceActions: 'always' });
vuex also integrates with Vue DevTools. You can inspect mutations and state changes over time, similar to Redux but tailored for Vue's component tree.
// vuex
// Vue DevTools shows a timeline of mutations.
// You can see the payload and state before/after each mutation.
redux is framework-agnostic but most commonly paired with React via react-redux. It requires binding libraries for other frameworks.
// redux + React
import { useSelector } from 'react-redux';
function Counter() {
const count = useSelector((state) => state.counter.value);
return <div>{count}</div>;
}
mobx works with React, Vue, and others. For React, mobx-react-lite provides observers. For Vue, it can work but is less common than Vuex/Pinia.
// mobx + React
import { observer } from 'mobx-react-lite';
const Counter = observer(() => {
return <div>{store.count}</div>;
});
vuex is officially maintained for Vue. It uses Vue's reactivity system internally. It does not support React or other frameworks natively.
// vuex + Vue
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
return { count: computed(() => store.state.count) };
}
};
redux remains the industry standard for React apps, though Redux Toolkit is now the required way to write Redux logic. The core redux package is in maintenance, with all new development focused on the Toolkit.
mobx is actively maintained and stable. It is a popular choice for teams who find Redux too verbose, though it has a smaller ecosystem than Redux.
vuex is in maintenance mode for Vue 3. The Vue team officially recommends Pinia for new Vue 3 projects. Vuex 5 was cancelled in favor of Pinia. Use Vuex primarily for Vue 2 legacy support.
| Feature | redux | mobx | vuex |
|---|---|---|---|
| Philosophy | Immutable, Functional | Mutable, OOP | Mutable, Centralized |
| Boilerplate | Medium (Low with Toolkit) | Low | Medium |
| DevTools | Excellent (Time Travel) | Good | Excellent (Vue DevTools) |
| Framework | Agnostic (React focused) | Agnostic | Vue Only |
| Learning Curve | Steep | Gentle | Moderate |
| Vue 3 Status | Compatible | Compatible | Maintenance (Pinia preferred) |
redux is the safe, enterprise-grade choice for React applications where predictability and tooling are paramount. It forces discipline that pays off in large teams.
mobx is the productive, flexible choice for developers who want to write less code and prefer object-oriented patterns. It shines in complex domains where Redux's boilerplate slows down iteration.
vuex is the legacy standard for Vue 2. If you are starting a new Vue 3 project, you should evaluate Pinia instead, as Vuex is no longer the primary recommendation from the Vue core team.
Final Thought: Your choice depends heavily on your framework. If you use Vue, look at Pinia first, then Vuex. If you use React, choose between Redux (for structure) and MobX (for speed and simplicity).
Choose mobx if you prefer a mutable, object-oriented approach that minimizes boilerplate and feels like standard JavaScript. It is ideal for teams that want reactive state without the strict structure of actions and reducers, especially in complex domains where data relationships are intricate. However, be aware that its magic can sometimes make data flow harder to trace in very large codebases.
Choose redux (specifically with Redux Toolkit) if you need a predictable, debuggable state container with a strict unidirectional data flow. It is the best fit for large-scale applications where many developers need a shared mental model of how data changes, and where time-travel debugging or extensive middleware ecosystems are required. It works well with React but requires more setup than other options.
Choose vuex if you are maintaining a Vue 2 application or a Vue 3 project that requires strict adherence to the classic Vue ecosystem patterns. However, for new Vue 3 projects, the Vue team officially recommends Pinia as the modern successor, so only select vuex if you have specific legacy requirements or need compatibility with existing Vue 2 plugins.
Simple, scalable state management.
Documentation can be found at mobx.js.org.
MobX is made possible by the generosity of the sponsors below, and many other individual backers. Sponsoring directly impacts the longevity of this project.
๐ฅ๐ฅ Platinum sponsors ($5000+ total contribution): ๐ฅ๐ฅ
๐ฅ Gold sponsors ($2500+ total contribution):
๐ฅ Silver sponsors ($500+ total contributions):
Anything that can be derived from the application state, should be. Automatically.
MobX is a signal based, battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming. The philosophy behind MobX is simple:
Write minimalistic, boilerplate-free code that captures your intent. Trying to update a record field? Simply use a normal JavaScript assignment โ the reactivity system will detect all your changes and propagate them out to where they are being used. No special tools are required when updating data in an asynchronous process.
All changes to and uses of your data are tracked at runtime, building a dependency tree that captures all relations between state and output. This guarantees that computations that depend on your state, like React components, run only when strictly needed. There is no need to manually optimize components with error-prone and sub-optimal techniques like memoization and selectors.
MobX is unopinionated and allows you to manage your application state outside of any UI framework. This makes your code decoupled, portable, and above all, easily testable.
So what does code that uses MobX look like?
import React from "react"
import ReactDOM from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react-lite"
// Model the application state.
function createTimer() {
return makeAutoObservable({
secondsPassed: 0,
increase() {
this.secondsPassed += 1
},
reset() {
this.secondsPassed = 0
}
})
}
const myTimer = createTimer()
// Build a "user interface" that uses the observable state.
const TimerView = observer(({ timer }) => (
<button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>
))
ReactDOM.render(<TimerView timer={myTimer} />, document.body)
// Update the 'Seconds passed: X' text every second.
setInterval(() => {
myTimer.increase()
}, 1000)
The observer wrapper around the TimerView React component will automatically detect that rendering
depends on the timer.secondsPassed observable, even though this relationship is not explicitly defined. The reactivity system will take care of re-rendering the component when precisely that field is updated in the future.
Every event (onClick / setInterval) invokes an action (myTimer.increase / myTimer.reset) that updates observable state (myTimer.secondsPassed).
Changes in the observable state are propagated precisely to all computations and side effects (TimerView) that depend on the changes being made.
This conceptual picture can be applied to the above example, or any other application using MobX.
To learn about the core concepts of MobX using a larger example, check out The gist of MobX page, or take the 10 minute interactive introduction to MobX and React. The philosophy and benefits of the mental model provided by MobX are also described in great detail in the blog posts UI as an afterthought and How to decouple state and UI (a.k.a. you donโt need componentWillMount).
The MobX Quick Start Guide ($24.99) by Pavan Podila and Michel Weststrate is available as an ebook, paperback, and on the O'Reilly platform (see preview).
MobX is inspired by reactive programming principles, which are for example used in spreadsheets. It is inspired by modelโviewโviewmodel frameworks like MeteorJS's Tracker, Knockout and Vue.js, but MobX brings transparent functional reactive programming (TFRP, a concept which is further explained in the MobX book) to the next level and provides a standalone implementation. It implements TFRP in a glitch-free, synchronous, predictable and efficient manner.
A ton of credit goes to Mendix, for providing the flexibility and support to maintain MobX and the chance to prove the philosophy of MobX in a real, complex, performance critical applications.