redux vs mobx vs recoil
React State Management Libraries
reduxmobxrecoilSimilar Packages:

React State Management Libraries

mobx, recoil, and redux are state management libraries for JavaScript applications, primarily used with React. They help manage shared, persistent state across components while providing mechanisms for updates, side effects, and performance optimization. mobx uses observable objects and automatic dependency tracking, recoil models state as fine-grained atoms and derived selectors, and redux enforces a single immutable store updated through pure reducer functions triggered by dispatched actions.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
redux28,745,02361,456290 kB412 years agoMIT
mobx028,1854.35 MB796 months agoMIT
recoil019,5162.21 MB3223 years agoMIT

State Management in React: MobX vs Recoil vs Redux

Choosing a state management solution is one of the most consequential decisions you’ll make when building a React application. mobx, recoil, and redux each offer distinct approaches to handling shared state, with trade-offs in complexity, performance, reactivity model, and developer experience. Let’s break down how they work under real-world conditions.

🧠 Core Philosophy: Observable Mutations vs Atomic Atoms vs Single Immutable Store

mobx treats state as observable objects that you mutate directly. Changes automatically trigger updates in components that depend on that state.

// mobx: observable state with direct mutation
import { makeAutoObservable } from 'mobx';

class CounterStore {
  count = 0;

  constructor() {
    makeAutoObservable(this);
  }

  increment() {
    this.count++; // Direct mutation
  }
}

const store = new CounterStore();

recoil models state as atoms (individual units) and selectors (derived state). You never mutate atoms directly — you replace their entire value.

// recoil: atomic state with immutable updates
import { atom, useSetRecoilState } from 'recoil';

const countState = atom({
  key: 'count',
  default: 0
});

function Counter() {
  const setCount = useSetRecoilState(countState);
  return <button onClick={() => setCount(c => c + 1)}>+</button>;
}

redux enforces a single, immutable store updated exclusively through pure functions called reducers triggered by actions.

// redux: single store, actions, and reducers
const increment = () => ({ type: 'INCREMENT' });

const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
};

🔄 Reactivity Model: Automatic Tracking vs Explicit Subscriptions

mobx uses transparent reactivity: it tracks which observables your component reads during rendering and automatically re-renders when those values change. No manual subscription needed.

// mobx: auto-tracking via observer
import { observer } from 'mobx-react-lite';

const CounterDisplay = observer(({ store }) => (
  <div>{store.count}</div> // Automatically tracked
));

recoil requires explicit hooks (useRecoilValue, useRecoilState) to subscribe to atoms or selectors. Reactivity is opt-in per component.

// recoil: explicit subscription
import { useRecoilValue } from 'recoil';

function CounterDisplay() {
  const count = useRecoilValue(countState); // Explicit subscription
  return <div>{count}</div>;
}

redux traditionally required manual subscription via connect() or useSelector. Modern Redux Toolkit simplifies this, but you still explicitly declare what slice of state you need.

// redux: explicit selector
import { useSelector } from 'react-redux';

function CounterDisplay() {
  const count = useSelector(state => state.counter.count);
  return <div>{count}</div>;
}

📦 State Structure: Decentralized Objects vs Granular Atoms vs Centralized Tree

mobx encourages domain-driven stores — you create classes or objects that encapsulate related state and logic. This scales well for complex domains but can lead to scattered state if not organized carefully.

// mobx: multiple stores
const userStore = new UserStore();
const cartStore = new CartStore();
// Passed via context or modules

recoil promotes fine-grained atoms, even for small pieces of state. This avoids unnecessary re-renders but can result in many atom declarations.

// recoil: many small atoms
const firstNameState = atom({ key: 'firstName', default: '' });
const lastNameState = atom({ key: 'lastName', default: '' });
// Or combine into one object atom if preferred

redux mandates a single state tree. All state lives in one big object, usually normalized. This provides a clear source of truth but can feel rigid for loosely coupled features.

// redux: single state tree
{
  users: { /* normalized */ },
  posts: { /* normalized */ },
  ui: { loading: false }
}

⚙️ Async Logic Handling: Where Side Effects Live

mobx lets you write async methods directly in stores using async/await. No middleware needed.

// mobx: async in store
fetchUser = async (id) => {
  this.loading = true;
  try {
    this.user = await api.getUser(id);
  } finally {
    this.loading = false;
  }
};

recoil uses asynchronous selectors or atom effects for side effects. Selectors can return promises, and components will render pending states automatically.

// recoil: async selector
const userQuery = selector({
  key: 'userQuery',
  get: async ({ get }) => {
    const id = get(userIdState);
    return await api.getUser(id);
  }
});

redux requires middleware like redux-thunk or redux-saga to handle async logic. Thunks are simple; sagas offer powerful control flow but add complexity.

// redux: thunk
const fetchUser = (id) => async (dispatch) => {
  dispatch({ type: 'FETCH_START' });
  const user = await api.getUser(id);
  dispatch({ type: 'FETCH_SUCCESS', payload: user });
};

🔌 Integration with React: Hooks, HOCs, and Context

mobx uses the observer higher-order component (or hook equivalent) to wrap components. It integrates via React context for store injection.

// mobx: provider and observer
import { Provider, observer } from 'mobx-react';

<Provider store={store}>
  <App />
</Provider>

const App = observer(() => <CounterDisplay />);

recoil requires a RecoilRoot at the top of your app. Components use hooks directly — no wrappers needed beyond the root.

// recoil: RecoilRoot
import { RecoilRoot } from 'recoil';

<RecoilRoot>
  <App />
</RecoilRoot>

redux needs a Provider wrapping your app, passing the store. Components use hooks like useSelector and useDispatch.

// redux: Provider
import { Provider } from 'react-redux';

<Provider store={store}>
  <App />
</Provider>

🛠 Developer Experience: Boilerplate vs Magic vs Discipline

mobx feels the most “magical” — you write plain JavaScript classes and mutations, and reactivity just works. But this can obscure data flow for newcomers.

recoil strikes a balance: minimal boilerplate, explicit subscriptions, and built-in async support. However, its API is less mature and adoption is narrower.

redux demands discipline: actions, reducers, selectors, middleware. But this structure pays off in large teams with strict debugging and time-travel requirements.

🔄 Real-World Trade-Offs Summary

Aspectmobxrecoilredux
Learning CurveModerate (concepts: observables)Low–Moderate (atoms/selectors)Steep (actions, reducers, middleware)
BoilerplateLowVery LowHigh
DebuggingGood (MobX DevTools)Limited (basic React DevTools)Excellent (Redux DevTools)
PerformanceExcellent (fine-grained updates)Excellent (per-atom subscriptions)Good (requires memoization/selectors)
Async SupportBuilt-in (async methods)Built-in (async selectors)Requires middleware
Team ScalabilityMedium (mutation-based logic)Medium (newer, less ecosystem)High (strict unidirectional flow)

💡 When to Choose Which

  • Choose mobx if you want to model state as mutable objects with automatic reactivity, prefer OOP-style stores, and value writing business logic close to your data. Ideal for medium-to-large apps where developer velocity matters more than strict functional purity.

  • Choose recoil if you’re starting a new React app and want fine-grained, hook-based state with minimal setup and built-in async capabilities. Best for teams comfortable with React hooks and willing to adopt a newer, less battle-tested library.

  • Choose redux if you need predictable state transitions, time-travel debugging, strong middleware ecosystem (logging, persistence, etc.), or are working in a large team that benefits from enforced structure. Still the gold standard for complex, long-lived applications where traceability is critical.

🤝 Final Thought

All three libraries solve real problems. mobx embraces mutability with smart tracking, recoil brings granular reactivity to the React hooks era, and redux offers unmatched predictability through immutability and unidirectional flow. Your choice should reflect your team’s tolerance for magic vs. discipline, your app’s complexity, and how much you value debuggability over convenience.

How to Choose: redux vs mobx vs recoil

  • redux:

    Choose redux if you need strict predictability, time-travel debugging, a rich middleware ecosystem (like Redux Toolkit, RTK Query, or sagas), or are working in a large team that benefits from enforced unidirectional data flow. It remains the strongest choice for complex, long-term applications where traceability, testing, and state serialization are critical.

  • mobx:

    Choose mobx if you prefer modeling state as mutable objects with automatic reactivity, want to colocate business logic with data in domain-driven stores, and value writing straightforward imperative code without boilerplate. It’s well-suited for medium to large applications where developer experience and rapid iteration are priorities, and you’re comfortable with mutation-based state updates.

  • recoil:

    Choose recoil if you’re building a new React application and want a lightweight, hooks-native solution with fine-grained reactivity, built-in async support via selectors, and minimal setup. It works best when your team embraces React’s modern patterns and doesn’t require extensive devtooling or a mature ecosystem.

README for redux

Redux Logo

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.

GitHub Workflow Status npm version npm downloads redux channel on discord

Installation

Create a React Redux App

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.

Documentation

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.

Learn Redux

Redux Essentials Tutorial

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.

Redux Fundamentals Tutorial

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.

Help and Discussion

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!

Before Proceeding Further

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:

  • You have reasonable amounts of data changing over time
  • You need a single source of truth for your state
  • You find that keeping all your state in a top-level component is no longer sufficient

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:

Basic Example

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.

Logo

You can find the official logo on GitHub.

Change Log

This project adheres to Semantic Versioning. Every release, along with the migration instructions, is documented on the GitHub Releases page.

License

MIT