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 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 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 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.
Redux is a predictable state container for JavaScript apps.
It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.
You can use Redux together with React, or with any other view library. The Redux core is tiny (2kB, including dependencies), and has a rich ecosystem of addons.
Redux Toolkit is our official recommended approach for writing Redux logic. It wraps around the Redux core, and contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications.
The recommended way to start new apps with React and Redux Toolkit is by using our official Redux Toolkit + TS template for Vite, or by creating a new Next.js project using Next's with-redux template.
Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features.
# Vite with our Redux+TS template
# (using the `degit` tool to clone and extract the template)
npx degit reduxjs/redux-templates/packages/vite-template-redux my-app
# Next.js using the `with-redux` template
npx create-next-app --example with-redux my-app
We do not currently have official React Native templates, but recommend these templates for standard React Native and for Expo:
npm install @reduxjs/toolkit react-redux
For the Redux core library by itself:
npm install redux
For more details, see the Installation docs page.
The Redux core docs are located at https://redux.js.org, and include the full Redux tutorials, as well usage guides on general Redux patterns:
The Redux Toolkit docs are available at https://redux-toolkit.js.org, including API references and usage guides for all of the APIs included in Redux Toolkit.
The Redux Essentials tutorial is a "top-down" tutorial that teaches "how to use Redux the right way", using our latest recommended APIs and best practices. We recommend starting there.
The Redux Fundamentals tutorial is a "bottom-up" tutorial that teaches "how Redux works" from first principles and without any abstractions, and why standard Redux usage patterns exist.
The #redux channel of the Reactiflux Discord community is our official resource for all questions related to learning and using Redux. Reactiflux is a great place to hang out, ask questions, and learn - please come and join us there!
Redux is a valuable tool for organizing your state, but you should also consider whether it's appropriate for your situation. Please don't use Redux just because someone said you should - instead, please take some time to understand the potential benefits and tradeoffs of using it.
Here are some suggestions on when it makes sense to use Redux:
Yes, these guidelines are subjective and vague, but this is for a good reason. The point at which you should integrate Redux into your application is different for every user and different for every application.
For more thoughts on how Redux is meant to be used, please see:
The whole global state of your app is stored in an object tree inside a single store. The only way to change the state tree is to create an action, an object describing what happened, and dispatch it to the store. To specify how state gets updated in response to an action, you write pure reducer functions that calculate a new state based on the old state and the action.
Redux Toolkit simplifies the process of writing Redux logic and setting up the store. With Redux Toolkit, the basic app logic looks like:
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
incremented: state => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decremented: state => {
state.value -= 1
}
}
})
export const { incremented, decremented } = counterSlice.actions
const store = configureStore({
reducer: counterSlice.reducer
})
// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()))
// Still pass action objects to `dispatch`, but they're created for us
store.dispatch(incremented())
// {value: 1}
store.dispatch(incremented())
// {value: 2}
store.dispatch(decremented())
// {value: 1}
Redux Toolkit allows us to write shorter logic that's easier to read, while still following the original core Redux behavior and data flow.
You can find the official logo on GitHub.
This project adheres to Semantic Versioning. Every release, along with the migration instructions, is documented on the GitHub Releases page.