@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.
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.
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 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.
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.
The official, opinionated, batteries-included toolset for efficient Redux development
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:
Redux Toolkit is available as a package on NPM for use with a module bundler or in a Node application:
# NPM
npm install @reduxjs/toolkit
# Yarn
yarn add @reduxjs/toolkit
The package includes a precompiled ESM build that can be used as a <script type="module"> tag directly in the browser.
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 core docs at https://redux.js.org includes the full Redux tutorials, as well usage guides on general Redux patterns.
The Redux Toolkit package is intended to be the standard way to write Redux logic. It was originally created to help address three common concerns about Redux:
We can't solve every use case, but in the spirit of create-react-app, we can try to provide some tools that abstract over the setup process and handle the most common use cases, as well as include some useful utilities that will let the user simplify their application code.
Because of that, this package is deliberately limited in scope. It does not address concepts like "reusable encapsulated Redux modules", folder or file structures, managing entity relationships in the store, and so on.
Redux Toolkit also includes a powerful data fetching and caching capability that we've dubbed "RTK Query". It's included in the package as a separate set of entry points. It's optional, but can eliminate the need to hand-write data fetching logic yourself.
Redux Toolkit includes these APIs:
configureStore(): wraps createStore to provide simplified configuration options and good defaults. It can automatically combine your slice reducers, add whatever Redux middleware you supply, includes redux-thunk by default, and enables use of the Redux DevTools Extension.createReducer(): lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the immer library to let you write simpler immutable updates with normal mutative code, like state.todos[3].completed = true.createAction(): generates an action creator function for the given action type string. The function itself has toString() defined, so that it can be used in place of the type constant.createSlice(): combines createReducer() + createAction(). Accepts an object of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types.combineSlices(): combines multiple slices into a single reducer, and allows "lazy loading" of slices after initialisation.createListenerMiddleware(): lets you define "listener" entries that contain an "effect" callback with additional logic, and a way to specify when that callback should run based on dispatched actions or state changes. A lightweight alternative to Redux async middleware like sagas and observables.createAsyncThunk(): accepts an action type string and a function that returns a promise, and generates a thunk that dispatches pending/resolved/rejected action types based on that promisecreateEntityAdapter(): generates a set of reusable reducers and selectors to manage normalized data in the storecreateSelector() utility from the Reselect library, re-exported for ease of use.For details, see the Redux Toolkit API Reference section in the docs.
RTK Query is provided as an optional addon within the @reduxjs/toolkit package. It is purpose-built to solve the use case of data fetching and caching, supplying a compact, but powerful toolset to define an API interface layer for your app. It is intended to simplify common cases for loading data in a web application, eliminating the need to hand-write data fetching & caching logic yourself.
RTK Query is built on top of the Redux Toolkit core for its implementation, using Redux internally for its architecture. Although knowledge of Redux and RTK are not required to use RTK Query, you should explore all of the additional global store management capabilities they provide, as well as installing the Redux DevTools browser extension, which works flawlessly with RTK Query to traverse and replay a timeline of your request & cache behavior.
RTK Query is included within the installation of the core Redux Toolkit package. It is available via either of the two entry points below:
import { createApi } from '@reduxjs/toolkit/query'
/* React-specific entry point that automatically generates
hooks corresponding to the defined endpoints */
import { createApi } from '@reduxjs/toolkit/query/react'
RTK Query includes these APIs:
createApi(): The core of RTK Query's functionality. It allows you to define a set of endpoints describe how to retrieve data from a series of endpoints, including configuration of how to fetch and transform that data. In most cases, you should use this once per app, with "one API slice per base URL" as a rule of thumb.fetchBaseQuery(): A small wrapper around fetch that aims to simplify requests. Intended as the recommended baseQuery to be used in createApi for the majority of users.<ApiProvider />: Can be used as a Provider if you do not already have a Redux store.setupListeners(): A utility used to enable refetchOnMount and refetchOnReconnect behaviors.See the RTK Query Overview page for more details on what RTK Query is, what problems it solves, and how to use it.
Please refer to our contributing guide to learn about our development process, how to propose bugfixes and improvements, and how to build and test your changes to Redux Toolkit.