Skip to content
C Codeloom
React

React Context vs Redux: When to Use Which

A practical comparison of React Context and Redux: rendering model, performance, devtools, and concrete heuristics for picking the right tool.

·5 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • What Context is actually designed for
  • How Redux differs as a state container
  • Re-render behavior of each approach
  • Performance tradeoffs in real apps
  • How to choose between them

Prerequisites

  • Comfortable with React hooks

“Should I use Context or Redux?” is one of the most repeated React questions, and the answer is usually “neither alone, and not for what you think.” Context is a dependency-injection mechanism, not a state manager. Redux is a state container with strict update rules. They solve overlapping but different problems, and you can use both in the same app without contradiction.

What and why

React Context lets a parent provide a value that any descendant can read, regardless of how deep the tree gets. It exists to avoid prop drilling. When the provider’s value changes, every consumer re-renders. There is no built-in selector, no diffing, no middleware. You hand it a value, and React broadcasts it.

Redux is a predictable state container. State lives in a single store. You dispatch actions, a reducer returns the new state, and components subscribe via selectors. React-Redux only re-renders a component when its selected slice changes. Redux Toolkit (RTK) is the modern flavor and removes most of the historical boilerplate.

Mental model

Context is a tube: anything you pour in at the top reaches anyone reading from it. Redux is a database: components query it and only react when their query result changes.

Context:
Provider value changes
     |
     v
All consumers re-render (no selector filtering)

Redux:
dispatch(action)
     |
     v
Reducer returns new state
     |
     v
Only components whose selector result changed re-render
Update propagation in Context vs Redux

The practical implication: if you put a frequently-updating value into Context, every consumer renders on every change. With Redux, only consumers whose selected slice changed will render.

Hands-on example

A theme toggle is a textbook Context use case: rarely changes, read everywhere.

const ThemeContext = createContext<'light' | 'dark'>('light');

function App() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  return (
    <ThemeContext.Provider value={theme}>
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        Toggle
      </button>
      <Page />
    </ThemeContext.Provider>
  );
}

function Page() {
  const theme = useContext(ThemeContext);
  return <div className={theme}>...</div>;
}

A shopping cart with many slices and frequent updates is a better fit for Redux:

// store/cartSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

type Item = { id: string; qty: number };

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] as Item[] },
  reducers: {
    add(state, action: PayloadAction<Item>) {
      const existing = state.items.find(i => i.id === action.payload.id);
      if (existing) existing.qty += action.payload.qty;
      else state.items.push(action.payload);
    },
    remove(state, action: PayloadAction<string>) {
      state.items = state.items.filter(i => i.id !== action.payload);
    },
  },
});

export const { add, remove } = cartSlice.actions;
export default cartSlice.reducer;
function CartCount() {
  const count = useSelector((s: RootState) => s.cart.items.length);
  return <span>{count}</span>;
}

CartCount only re-renders when the item count changes, not when quantities of existing items mutate. That granularity is the value Redux adds.

Common pitfalls

  • Putting changing values into Context without memoizing them. Every parent render creates a new object identity, even if the contents are the same, which causes consumer re-renders.
  • Wrapping the entire app in a single giant Context that holds dozens of unrelated fields. Splitting Context by domain is almost always better.
  • Reaching for Redux for a tiny piece of UI state. A useState in a parent is fine for most local concerns.
  • Using selectors that return new arrays or objects on each call. Without a shallow or deep equality check, you trigger unnecessary re-renders.

Best practices

  • Context for: theme, locale, auth user, current route, feature flags. Things that change rarely and are read widely.
  • Redux for: domain state that many components write to and read from, derived data, undo/redo, and time-travel debugging.
  • Use multiple small contexts instead of one large one. Group values that change together.
  • For Redux, always use Redux Toolkit. The hand-written switch statements and action constants are no longer required.
  • If you need cross-component state without Redux, consider Zustand or Jotai. They give you selector-based subscriptions without the Redux scaffolding.

FAQ

Does Context make the app slower? Only if you push high-frequency updates through it. For static-ish values it’s fine.

Can I use both? Yes, and most large apps do. Auth and theme in Context; domain data in Redux or Zustand.

Is Redux dead? No. RTK and RTK Query are very much alive and remain the default at many large companies because of their debugging story and middleware ecosystem.

What about React’s use() and Server Components? Server data fetched with use() lives outside Redux. You typically use Redux for client-side mutations and Server Components for read-paths.