mobx, mobx-state-tree, redux, and vuex are state management libraries designed to handle application data flow in frontend applications. mobx provides transparent reactive programming by making state observable and automatically tracking dependencies. mobx-state-tree builds on MobX with explicit, typed models, snapshots, and middleware for structured state trees. redux enforces a predictable, immutable state container using actions and pure reducer functions, often enhanced with middleware. vuex is a centralized state management pattern specifically for Vue.js applications, using mutations for synchronous state changes and actions for asynchronous logic. Note that Vuex is now in maintenance mode, with Pinia recommended for new Vue projects.
State management is a core challenge in frontend development. The libraries mobx, mobx-state-tree, redux, and vuex each offer distinct approaches to modeling, updating, and observing application state. While all solve the same fundamental problem — keeping UI in sync with data — their philosophies, APIs, and trade-offs differ significantly. Let’s compare them from an engineering perspective.
mobx treats state as reactive objects. You write plain JavaScript classes or objects, decorate properties as observable, and let MobX automatically track which components depend on which values. Changes trigger re-renders only where needed.
// mobx
import { makeObservable, observable, action } from "mobx";
class Counter {
count = 0;
constructor() {
makeObservable(this, {
count: observable,
increment: action
});
}
increment() {
this.count++;
}
}
mobx-state-tree (MST) builds on MobX but enforces explicit models with types, snapshots, and actions. It combines immutability (via snapshots) with mutability (inside actions), offering runtime type checking and time-travel debugging.
// mobx-state-tree
import { types } from "mobx-state-tree";
const CounterModel = types
.model({ count: 0 })
.actions(self => ({
increment() {
self.count++;
}
}));
const counter = CounterModel.create();
redux follows a strict unidirectional data flow: state is immutable, updated only by pure reducer functions in response to dispatched actions. Everything is explicit — no magic tracking.
// redux
const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
}
// Dispatch
store.dispatch({ type: 'INCREMENT' });
vuex is tightly integrated with Vue.js, using a centralized store with state, mutations (synchronous state changes), and actions (asynchronous logic). It leverages Vue’s reactivity system under the hood.
// vuex (Vue 2/3 Options API style)
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) {
state.count++;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => commit('increment'), 100);
}
}
});
⚠️ Important Note: As of 2024, Vuex is in maintenance mode. The official Vuex documentation states: “Vuex 4 will be the last version of Vuex. It has entered maintenance mode.” For new Vue 3 projects, the Vue team recommends Pinia instead. Therefore,
vuexshould not be used in new applications.
Each library enforces different rules about how state can be modified.
mobx: Mutate observables directly inside action functions (or allow direct mutation if using configure({ enforceActions: "never" })). No immutability required.// mobx: direct mutation allowed in actions
store.increment(); // modifies this.count in place
mobx-state-tree: State is mutable only inside actions. Outside actions, the tree is effectively immutable. All changes are recorded for devtools.// MST: mutation only in actions
counter.increment(); // OK
// counter.count = 5; // throws error outside action
redux: State is always immutable. Reducers must return new objects. Libraries like Immer can help with ergonomic updates.// redux with immer (common pattern)
import produce from "immer";
const reducer = produce((draft, action) => {
if (action.type === 'INCREMENT') draft.count += 1;
}, initialState);
vuex: State changes must go through mutations, which are synchronous. This ensures devtools can track every change.// vuex: commit mutation
store.commit('increment');
How each library connects state to UI components affects performance and developer experience.
mobx: Uses transparent reactivity. Components wrapped with observer automatically re-render when any observed value changes — no need to specify dependencies.// mobx + React
import { observer } from "mobx-react-lite";
const CounterView = observer(({ counter }) => (
<div>{counter.count}</div>
));
mobx-state-tree: Inherits MobX’s reactivity. Same observer pattern applies.
redux: Requires manual subscription or use of hooks like useSelector. You explicitly declare which state slices your component needs.
// redux + React
import { useSelector } from "react-redux";
const CounterView = () => {
const count = useSelector(state => state.counter.count);
return <div>{count}</div>;
};
vuex: In Vue, you access store state via this.$store (Options API) or useStore() (Composition API). Vue’s reactivity system handles updates automatically.// vuex + Vue (Composition API)
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
const count = computed(() => store.state.count);
return { count };
}
};
mobx: DevTools show which observables triggered which reactions. Less structured than Redux but very intuitive.
mobx-state-tree: Offers time-travel debugging, runtime type validation, and automatic snapshot serialization. Excellent for complex domain models.
// MST: get serializable snapshot
const json = getSnapshot(counter);
redux: Mature ecosystem with Redux DevTools that log every action, enable time travel, and inspect state diffs. Widely understood by teams.
vuex: Integrates with Vue DevTools, showing mutation history and state changes. However, since Vuex is deprecated, tooling investment has shifted to Pinia.
mobx: Scales via class composition or module splitting. Easy to start, but large apps may lack structure without discipline.
mobx-state-tree: Designed for composable models. Trees can reference each other, support middleware, and enforce domain boundaries.
// MST: model composition
const Store = types.model({
user: UserModel,
todos: TodoListModel
});
redux: Scales via reducer composition (combineReducers) and middleware (e.g., Redux Toolkit simplifies this dramatically). Strong conventions prevent drift.
vuex: Supports modules for namespacing, but module boilerplate can feel heavy. Again, not recommended for new projects.
mobx and mobx-state-tree work with any UI framework (React, Vue, Angular, Svelte) via thin bindings.
redux is framework-agnostic but most commonly used with React (via react-redux). Works anywhere you can call dispatch and getState.
vuex is exclusive to Vue.js. Cannot be used outside the Vue ecosystem.
| Scenario | Best Fit |
|---|---|
| Rapid prototyping with minimal boilerplate | mobx |
| Complex domain models requiring validation, snapshots, and time-travel | mobx-state-tree |
| Large team needing strict conventions, auditability, and middleware | redux (preferably with Redux Toolkit) |
| New Vue 3 project | Not vuex — use Pinia instead |
| Feature | mobx | mobx-state-tree | redux | vuex |
|---|---|---|---|---|
| Reactivity Model | Transparent tracking | Transparent + snapshots | Explicit selectors | Vue reactivity |
| State Mutability | Mutable (in actions) | Mutable (in actions only) | Immutable | Mutable (via mutations) |
| Boilerplate | Low | Medium | Medium–High (lower with RTK) | Medium |
| Type Safety | Good (with TS) | Excellent (runtime + compile-time) | Good (with TS) | Fair |
| DevTools | MobX DevTools | MST DevTools + time travel | Redux DevTools | Vue DevTools |
| Framework Lock-in | None | None | None | Vue only |
| Recommended for New Projects? | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No (use Pinia) |
mobx gets you moving fast.mobx-state-tree is unmatched.redux (with Redux Toolkit) remains a rock-solid choice.vuex for new projects — the Vue team has moved on to Pinia, which offers a cleaner, more composable API.Choose based on your team’s tolerance for magic vs. ceremony, your need for runtime guarantees, and whether you’re tied to a specific UI framework.
Choose redux—especially with Redux Toolkit—when working in large teams that benefit from strict conventions, comprehensive devtooling, middleware ecosystems (like Redux Thunk or Saga), and predictable, testable state transitions. It’s a mature choice for enterprise applications where auditability and consistency are critical.
Choose mobx when you need a lightweight, reactive state solution with minimal boilerplate and you're comfortable with mutable state. It’s ideal for small to medium applications or teams that prioritize developer velocity over strict architectural constraints. Its transparent reactivity works well with React, Vue, or any view layer.
Do not choose vuex for new projects. The Vue team has officially deprecated it in favor of Pinia, which offers a simpler, more composable, and TypeScript-friendly API. If maintaining a legacy Vue 2 or early Vue 3 app that already uses Vuex, continue using it—but migrate to Pinia when feasible.
Choose mobx-state-tree when you require strong runtime guarantees, such as type safety, automatic serialization, time-travel debugging, and explicit action boundaries. It’s best suited for complex domain models (e.g., editors, dashboards, or data-intensive apps) where structure and maintainability outweigh the cost of additional setup.
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.