@reduxjs/toolkit, mobx, redux, redux-saga, and vuex are state management libraries designed to help developers manage application state in a predictable and maintainable way. redux provides a strict unidirectional data flow with immutable state updates. @reduxjs/toolkit is the official, opinionated toolset for Redux that simplifies common use cases and reduces boilerplate. mobx uses transparent reactive programming to automatically track and update state dependencies. redux-saga is a middleware for Redux that handles side effects like data fetching using generator functions. vuex is the official state management pattern and library for Vue.js applications, though it has been superseded by Pinia for new Vue projects.
Managing application state is a core challenge in frontend development. The libraries @reduxjs/toolkit, mobx, redux, redux-saga, and vuex each offer distinct approaches to this problem. While they share the common goal of making state predictable and manageable, their philosophies, APIs, and trade-offs differ significantly. Let’s examine how they work under real-world conditions.
redux enforces strict immutability and unidirectional data flow. Every state change must happen through pure reducer functions that return new state objects.
// redux: immutable reducer
const counterReducer = (state = 0, action) => {
if (action.type === 'increment') {
return state + 1;
}
return state;
};
@reduxjs/toolkit keeps Redux’s immutability rules but simplifies them using Immer under the hood, letting you write "mutating" logic that’s safely converted to immutable updates.
// @reduxjs/toolkit: "mutable" syntax with Immer
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: (state) => {
// This looks like mutation but isn't
return state + 1;
}
}
});
mobx takes a reactive programming approach. You mark state as observable, and any code that reads it automatically re-runs when the state changes — no explicit dispatching or reducers needed.
// mobx: reactive state
import { makeAutoObservable } from 'mobx';
class Counter {
value = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
this.value += 1; // Direct mutation
}
}
vuex follows Vue’s reactivity system. State lives in a centralized store, and mutations must be committed via named functions, but the underlying reactivity is handled by Vue itself.
// vuex: mutation-based updates
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) {
state.count += 1; // Allowed because Vue tracks reactivity
}
}
});
redux-saga isn’t a standalone state manager — it’s a middleware for Redux that handles side effects (like API calls) using generator functions. It assumes you’re already using redux.
// redux-saga: side effect handling
import { call, put, takeEvery } from 'redux-saga/effects';
function* fetchUser(action) {
try {
const user = yield call(api.fetchUser, action.payload.userId);
yield put({ type: 'USER_LOADED', payload: user });
} catch (e) {
yield put({ type: 'USER_LOAD_FAILED', error: e.message });
}
}
function* watchFetchUser() {
yield takeEvery('FETCH_USER', fetchUser);
}
redux is known for its boilerplate. You need actions, action creators, reducers, and often middleware just for basic functionality.
// redux: full boilerplate
const INCREMENT = 'INCREMENT';
const decrement = () => ({ type: 'DECREMENT' });
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT: return state + 1;
case 'DECREMENT': return state - 1;
default: return state;
}
};
@reduxjs/toolkit dramatically reduces that boilerplate by combining actions and reducers, auto-generating action creators, and including Redux Thunk by default.
// @reduxjs/toolkit: minimal setup
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
}
}
});
// Actions are auto-generated:
// counterSlice.actions.increment()
mobx feels almost magical — you mutate state directly, and components update automatically. But this can obscure data flow, making debugging harder in large apps.
// mobx: direct mutation
const store = new Counter();
store.increment(); // That's it
vuex strikes a middle ground: mutations are explicit, but you don’t manage immutability manually. However, strict mode (which prevents direct state mutation outside mutations) is only enabled in development.
// vuex: commit mutation
store.commit('increment');
redux-saga adds significant complexity due to generator syntax and saga management, but gives fine-grained control over async flows (e.g., cancellation, race conditions).
// redux-saga: complex flow
function* loginFlow() {
while (true) {
const { credentials } = yield take('LOGIN_REQUEST');
yield fork(attemptLogin, credentials);
}
}
redux and @reduxjs/toolkit are framework-agnostic but most commonly used with React via react-redux. They integrate well with DevTools, time-travel debugging, and middleware ecosystems.
mobx works with any UI layer (React, Vue, Angular) via official bindings like mobx-react. Its reactivity model maps naturally to component re-renders.
vuex is tightly coupled to Vue 2. For Vue 3, the official recommendation is to use Pinia instead. Vuex 4 exists for Vue 3 compatibility, but Vuex is effectively in maintenance mode.
⚠️ Important: According to the Vuex npm page, "Vuex 5 is coming... but for new projects, consider using Pinia." The Vue team now recommends Pinia as the standard state management solution.
redux-saga only makes sense if you’re already using Redux. It’s not a replacement for Redux — it’s an add-on for managing side effects.
@reduxjs/toolkit includes Redux Thunk by default, letting you write async logic in plain functions:
// @reduxjs/toolkit + Thunk
const fetchUser = (userId) => async (dispatch) => {
const user = await api.getUser(userId);
dispatch(userLoaded(user));
};
redux-saga uses generators for more advanced control:
// redux-saga: cancellation example
function* todoSaga() {
const task = yield fork(fetchTodos);
yield take('CANCEL_FETCH');
yield cancel(task);
}
mobx lets you write async logic directly in store methods:
// mobx: async in store
loadUser = async (id) => {
this.loading = true;
try {
this.user = await api.getUser(id);
} finally {
this.loading = false;
}
};
vuex uses actions for async operations, which then commit mutations:
// vuex: actions for async
actions: {
fetchUser({ commit }, userId) {
api.getUser(userId).then(user => {
commit('setUser', user);
});
}
}
@reduxjs/toolkit if:mobx if:redux (plain) in new projects:@reduxjs/toolkit is the official, recommended way to use Redux today.redux-saga only if:vuex for new Vue projects:These tools reflect different philosophies:
Choose based on your team’s preferences for explicitness vs magic, your framework constraints, and whether you need advanced async control. For most modern React apps, @reduxjs/toolkit is the sweet spot. For Vue 3+, look at Pinia, not Vuex.
Avoid plain redux for new projects. While it established foundational patterns for predictable state management, @reduxjs/toolkit now provides the same guarantees with far less boilerplate and better developer experience. Only consider plain Redux if you have very specific constraints that prevent using Toolkit or if you're maintaining a legacy codebase that hasn't migrated yet.
Choose @reduxjs/toolkit if you're building a React application and want the benefits of Redux—predictable state, excellent DevTools, and a strong ecosystem—but without the excessive boilerplate. It's the officially recommended approach for Redux development today and includes best practices like Immer for simpler immutable updates and Redux Thunk for basic async logic out of the box.
Choose mobx if you prefer a more direct, object-oriented approach to state management where you can mutate state naturally and have components automatically re-render when relevant data changes. It works well with any UI framework and shines in applications with complex state relationships where fine-grained reactivity improves performance, but be aware that its 'magic' can make debugging harder in large teams.
Do not choose vuex for new Vue projects. The Vue team has shifted focus to Pinia as the official state management solution for Vue 3+. Vuex remains viable only for maintaining existing Vue 2 applications, as it's effectively in maintenance mode with no major new features planned.
Choose redux-saga only if you're already using Redux and need advanced control over complex asynchronous workflows—such as cancellation, debouncing, or intricate coordination between multiple side effects. It adds significant complexity with generator functions, so it's overkill for simple data fetching; use it when Redux Thunk (included in Toolkit) isn't sufficient.
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.