redux vs react-redux vs mobx vs recoil vs kea
State Management Solutions for React Applications
reduxreact-reduxmobxrecoilkeaSimilar Packages:
State Management Solutions for React Applications

kea, mobx, react-redux, recoil, and redux are all libraries designed to manage application state in JavaScript applications, particularly those built with React. They provide mechanisms to store, update, and access shared data across components while maintaining predictable behavior and performance. redux is a standalone, framework-agnostic state container that enforces a unidirectional data flow using actions and reducers. react-redux is the official React binding for Redux, enabling efficient component integration. recoil is a state management library developed by Meta that uses atoms and selectors to model state as a directed graph. mobx takes a reactive programming approach, automatically tracking observable state changes and re-rendering affected components. kea builds on Redux concepts but offers a more declarative, logic-centric API with built-in side effect handling.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
redux18,284,06861,415290 kB482 years agoMIT
react-redux13,835,99923,509823 kB34a year agoMIT
mobx2,430,78428,1154.35 MB782 months agoMIT
recoil528,78719,5632.21 MB3233 years agoMIT
kea22,4521,985348 kB74 months agoMIT

State Management Deep Dive: kea vs mobx vs react-redux vs recoil vs redux

Managing state in React apps goes beyond useState. When you need shared, persistent, or complex state, choosing the right library affects code clarity, performance, and maintainability. Let’s compare these five approaches through real engineering lenses.

🧠 Core Mental Models: How Each Library Thinks About State

redux treats state as immutable snapshots. You dispatch plain objects (actions) that describe what happened, and pure functions (reducers) compute the next state. Everything flows one way: action → reducer → new state.

// redux: Action + Reducer
const increment = { type: 'counter/increment' };

function counterReducer(state = 0, action) {
  if (action.type === 'counter/increment') {
    return state + 1;
  }
  return state;
}

react-redux connects React components to a Redux store. It uses useSelector to read state and useDispatch to send actions.

// react-redux: Component binding
import { useSelector, useDispatch } from 'react-redux';

function Counter() {
  const count = useSelector(state => state.counter);
  const dispatch = useDispatch();
  return <button onClick={() => dispatch(increment)}>{count}</button>;
}

recoil models state as atoms (shared units) and selectors (derived/computed values). Components subscribe only to the atoms/selectors they use.

// recoil: Atom + Selector
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

const countAtom = atom({ key: 'count', default: 0 });
const doubledCount = selector({
  key: 'doubledCount',
  get: ({ get }) => get(countAtom) * 2
});

function Counter() {
  const [count, setCount] = useRecoilState(countAtom);
  const double = useRecoilValue(doubledCount);
  return <div>{count} → {double}</div>;
}

mobx uses observable objects. You mark properties as observable, and any function reading them (like a React component) automatically re-runs when they change.

// mobx: Observable state
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

class CounterStore {
  count = 0;
  constructor() { makeAutoObservable(this); }
  increment() { this.count += 1; }
}

const store = new CounterStore();

const Counter = observer(() => (
  <button onClick={() => store.increment()}>{store.count}</button>
));

kea wraps Redux patterns into logic objects. Each logic defines actions, reducers, and side effects in one place, then connects to components.

// kea: Logic object
import { kea } from 'kea';

const counterLogic = kea({
  actions: { increment: true },
  reducers: { count: [0, { increment: (state) => state + 1 }] },
});

function Counter() {
  const { count } = counterLogic.useValues();
  const { increment } = counterLogic.useActions();
  return <button onClick={increment}>{count}</button>;
}

⚙️ Handling Side Effects: Async Operations and Beyond

redux doesn’t handle async natively. You need middleware like redux-thunk or redux-saga.

// redux + thunk: Async action
const fetchUser = (id) => async (dispatch) => {
  dispatch({ type: 'user/loading' });
  const user = await api.getUser(id);
  dispatch({ type: 'user/success', payload: user });
};

react-redux uses the same middleware — it just provides hooks to dispatch these thunks.

// react-redux: Dispatching thunk
const dispatch = useDispatch();
useEffect(() => { dispatch(fetchUser(123)); }, []);

recoil handles async in selectors using async/await. The framework manages loading/error states automatically.

// recoil: Async selector
const userQuery = selector({
  key: 'userQuery',
  get: async ({ get }) => {
    const response = await fetch(`/api/user/${get(userIdAtom)}`);
    return response.json();
  }
});

// In component:
const user = useRecoilValue(userQuery); // Suspends on pending

mobx lets you write async logic directly in store methods. Use runInAction to batch updates after async calls.

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

kea includes built-in sagas/thunks. Define listeners that respond to actions.

// kea: Built-in side effects
const userLogic = kea({
  actions: { loadUser: (id) => ({ id }), setUser: (user) => ({ user }) },
  reducers: { user: [null, { setUser: (_, payload) => payload.user }] },
  listeners: ({ actions }) => ({
    loadUser: async ({ id }) => {
      const user = await api.getUser(id);
      actions.setUser(user);
    }
  })
});

📦 Granularity and Performance: What Triggers Re-renders?

redux / react-redux: By default, every dispatched action causes all useSelector calls to re-run. You must memoize selectors (createSelector) or use shallow equality checks to prevent unnecessary renders.

// react-redux: Memoized selector
const selectUser = createSelector(
  [state => state.users],
  users => users.find(u => u.id === 123)
);
const user = useSelector(selectUser); // Only changes if user object changes

recoil: Components re-render only when their specific atoms/selectors change. Fine-grained by design.

// recoil: Automatic granularity
const firstName = useRecoilValue(firstNameAtom); // Only updates if firstName changes

mobx: Reactivity is property-level. If a component reads store.user.name, it only re-renders when name changes — not when other parts of user update.

// mobx: Property-level tracking
const NameDisplay = observer(() => <span>{store.user.name}</span>);
// Won't re-render if store.user.email changes

kea: Uses Redux under the hood, so same granularity rules apply. But its useValues hook auto-memoizes based on the logic’s reducers.

// kea: Auto-memoized values
const { user } = userLogic.useValues(); // Only changes if user reducer updates

🔌 Ecosystem and Tooling: Debugging and Extensibility

redux has unmatched tooling: Redux DevTools shows every action, lets you time-travel, and inspect state diffs. Middleware ecosystem is vast (persistence, logging, etc.).

react-redux inherits all Redux tooling plus React-specific optimizations (e.g., batch for multiple dispatches).

recoil offers Recoil DevTools (community-driven), but no official time-travel. Async handling is built-in, but SSR requires extra setup (<RecoilRoot> hydration).

mobx provides MobX Developer Tools to track observables and reactions. Debugging focuses on which observables triggered updates, not action history.

kea integrates with Redux DevTools out of the box. Its logic objects appear as named Redux slices, making debugging familiar to Redux users.

🧪 Testing and Predictability

redux shines here: reducers are pure functions, easy to unit test. Actions are serializable, enabling replayable logs.

// redux: Testable reducer
test('counter increments', () => {
  expect(counterReducer(0, increment)).toBe(1);
});

mobx tests require instantiating stores and checking observable state. Mutations are less explicit, so tests may be more brittle.

// mobx: Store testing
test('counter increments', () => {
  const store = new CounterStore();
  store.increment();
  expect(store.count).toBe(1);
});

recoil, kea, and react-redux fall in between: recoil selectors are pure functions (testable), kea logic can be tested via its actions/reducers, and react-redux relies on Redux’s testability.

🚫 Deprecation and Maintenance Status

As of 2024:

  • redux, react-redux, and mobx are actively maintained with stable APIs.
  • recoil is still used at Meta but has seen slower public development; no deprecation notice, but evaluate long-term support for enterprise use.
  • kea is actively maintained (v3+ supports modern React) with regular releases.

None of these packages are officially deprecated. All are viable for new projects, but consider team familiarity and project scale.

💡 When to Reach for Which

  • Need strict auditability and time-travel?redux + react-redux
  • Prefer writing mutations but want reactivity?mobx
  • Building a new app with async-heavy state?recoil (if comfortable with its maturity)
  • Want Redux power without boilerplate?kea
  • Already in a Redux ecosystem? → Stick with react-redux

📊 Summary Table

Featurereduxreact-reduxrecoilmobxkea
State ModelImmutableImmutableAtomsObservableRedux-based
Async HandlingMiddlewareMiddlewareSelectorsDirectBuilt-in
GranularityManualManualAutomaticProperty-levelManual
DevToolsExcellentExcellentBasicGoodRedux DevTools
BoilerplateHighMediumLowLowMedium
Learning CurveSteepMediumMediumGentleMedium

🎯 Final Thought

There’s no “best” state manager — only what fits your team’s mental model and app’s needs. If you value explicitness and tooling, lean toward Redux variants. If you prefer writing natural mutations, MobX feels effortless. For new greenfield projects wanting fine-grained reactivity, Recoil or Kea offer compelling middle grounds. Always prototype with real use cases before committing.

How to Choose: redux vs react-redux vs mobx vs recoil vs kea
  • redux:

    Choose redux if you need a battle-tested, framework-agnostic state container with strong guarantees around predictability, middleware support, and developer tooling. It’s best paired with Redux Toolkit to reduce boilerplate. Ideal for complex applications requiring time-travel debugging, persistence, or strict separation of concerns. Avoid it for simple UIs where local component state suffices, or if your team prefers mutable state models.

  • react-redux:

    Choose react-redux if you’re already using Redux or need tight integration with the Redux ecosystem (middleware like Redux Toolkit, Redux DevTools, time-travel debugging). It’s ideal for large-scale applications requiring strict unidirectional data flow, middleware extensibility, and strong community tooling. Avoid it if you’re building a small app where Redux’s boilerplate outweighs its benefits, or if you prefer more granular reactivity over global store subscriptions.

  • mobx:

    Choose mobx if you prioritize developer ergonomics and want to write state updates as direct mutations while still getting automatic reactivity. It shines in applications with complex, interdependent state where fine-grained reactivity reduces unnecessary re-renders. However, avoid it if your team requires strict immutability, auditability via action logs, or if you’re building in an environment where transparent reactivity could obscure data flow (e.g., large teams needing explicit traceability).

  • recoil:

    Choose recoil if you’re building a new React application and want fine-grained, asynchronous state dependencies with minimal boilerplate. Its atom-and-selector model works well for dynamic UIs with derived state and async queries. However, note that while actively used at Meta, its public roadmap has slowed; avoid it for mission-critical enterprise apps if long-term maintenance risk is a concern, and consider alternatives if you need server-side rendering compatibility out of the box.

  • kea:

    Choose kea if you want Redux-like predictability and devtools support but prefer a more declarative, colocated logic pattern without writing boilerplate actions and reducers. It’s well-suited for medium-to-large apps where you need structured state management with built-in sagas or thunks, and you value colocating business logic with components. Avoid it if your team isn’t comfortable with Redux concepts or if you need maximum simplicity for small projects.

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