React Context API vs Redux
Both Context API and Redux manage state that multiple components need to share. The choice between them depends on app size, state complexity, and team experience. This topic explains how each works, when to use which, and how they compare side by side.
What Problem Both Solve
When deeply nested components need access to shared data — like the current user, theme, or shopping cart — passing props through every level becomes tedious. Both Context and Redux let any component access shared state directly.
Context API: Built-in React Tool
Context is part of React itself — no extra installation needed. It is best suited for global data that changes infrequently: current user, app theme, language preference.
How Context works
// 1. Create
const CartContext = createContext(null);
// 2. Provide
function App() {
const [cart, setCart] = useState([]);
return (
<CartContext.Provider value={{ cart, setCart }}>
<ShopLayout />
</CartContext.Provider>
);
}
// 3. Consume (in any child, at any depth)
function CartIcon() {
const { cart } = useContext(CartContext);
return <span>{cart.length} items</span>;
}
Redux: Dedicated State Management Library
Redux stores the entire application state in one central object called the store. Components read from the store and send actions to update it. Updates flow through a pure function called a reducer.
Redux data flow diagram
Component
│
│ dispatch(action)
▼
Redux Store
│
│ passes action to
▼
Reducer function
│
│ returns new state
▼
Redux Store (updated)
│
│ notifies subscribed components
▼
Component re-renders with new state
Redux Toolkit (Modern Redux)
Redux Toolkit is the official, recommended way to write Redux. It removes most boilerplate code.
// store.js
import { configureStore, createSlice } from "@reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: { items: [], total: 0 },
reducers: {
addItem: (state, action) => {
state.items.push(action.payload); // Immer allows "mutation" syntax
state.total += action.payload.price;
},
removeItem: (state, action) => {
state.items = state.items.filter(item => item.id !== action.payload);
},
clearCart: (state) => {
state.items = [];
state.total = 0;
},
},
});
export const { addItem, removeItem, clearCart } = cartSlice.actions;
export const store = configureStore({
reducer: { cart: cartSlice.reducer },
});
// main.jsx — wrap app with Provider
import { Provider } from "react-redux";
import { store } from "./store";
ReactDOM.createRoot(document.getElementById("root")).render(
<Provider store={store}>
<App />
</Provider>
);
// Any component
import { useSelector, useDispatch } from "react-redux";
import { addItem, removeItem } from "./store";
function ProductCard({ product }) {
const dispatch = useDispatch();
const cartCount = useSelector((state) => state.cart.items.length);
return (
<div>
<h3>{product.name}</h3>
<button onClick={() => dispatch(addItem(product))}>
Add to Cart
</button>
<p>Cart: {cartCount} items</p>
</div>
);
}
Direct Comparison
Feature Context API Redux (Toolkit)
----------------------------------------------------------------
Installation None (built-in) npm install
Learning curve Low Medium
Setup effort Low Medium
DevTools support None Excellent
Time-travel debugging No Yes
Middleware support No Yes
Performance (many updates) Can be slow Optimized
Best for Simple/medium apps Large/complex apps
State size Small to medium Any size
Re-render optimization Manual Built-in (selectors)
When Context Causes Performance Issues
Every component that consumes a Context re-renders when the context value changes. If the context value updates frequently (e.g., every keystroke), many components re-render unnecessarily.
// PROBLEM: typing in search box changes context → everything re-renders
const AppContext = createContext();
function App() {
const [searchQuery, setSearchQuery] = useState("");
const [user, setUser] = useState(null);
// Both values in one context — ANY change re-renders ALL consumers
return (
<AppContext.Provider value={{ searchQuery, setSearchQuery, user }}>
<Navbar />
<SearchBar />
<ProductGrid /> {/* Re-renders on every keystroke! */}
</AppContext.Provider>
);
}
Fix: split high-frequency state (search query) from low-frequency state (user) into separate contexts or move fast-changing state to Redux selectors.
Choosing the Right Tool
Scenario Use
-------------------------------------------------
Current logged-in user Context
App theme (dark/light) Context
Language / locale Context
Shopping cart with many operations Redux
Large app with complex state interactions Redux
Multiple data types that depend on each Redux
other (user + permissions + cart + orders)
Form state in one component useState
Shared state across 2-3 nearby components useState + lift up
Can You Use Both?
Yes. Many real applications use both together. Redux manages the complex, frequently changing application state. Context handles simple, slow-changing global values like theme or locale. They do not conflict.
function App() {
return (
<Provider store={reduxStore}> {/* Redux for app state */}
<ThemeContext.Provider value={theme}> {/* Context for theme */}
<MainApp />
</ThemeContext.Provider>
</Provider>
);
}
Redux DevTools: A Major Advantage
Redux has browser DevTools that let you inspect every action dispatched, see the state before and after each action, and replay previous actions to reproduce bugs. Context has no equivalent tooling. For large teams working on complex apps, this debugging power alone often justifies choosing Redux.
Summary
Context API is built into React, requires no installation, and works well for simple shared state that changes infrequently. Redux provides a structured, predictable state container with excellent DevTools, middleware support, and optimized re-renders — best for large apps with complex state interactions. Redux Toolkit removes the boilerplate that made original Redux verbose. Start with Context for small to medium apps. Add Redux Toolkit when state complexity grows, debugging becomes difficult, or multiple developers need clear state change patterns.
