DEV Community

Cover image for React Context vs Redux: What to Use When?
Patoliya Infotech
Patoliya Infotech

Posted on

React Context vs Redux: What to Use When?

React's state management may have a big influence on both the developer experience and the project's architecture. Two popular methods are Redux, a popular external package, and React Context, a native feature. Both address the problem of data flow between components, but in quite different ways.

In this comprehensive guide, I’ll walk you through:

  • What React Context and Redux really are
  • Their core differences
  • When to use which — with practical code examples
  • Performance implications
  • Pro tips for real-world projects

You will have the knowledge and skills necessary to select the best state management approach for your React application at the end.

Quick Recap — What Are React Context and Redux?

React Context — Built-in State Sharing

React Context removes the requirement for "prop drill"—passing props through many levels—and enables data sharing between components. It functions well with basic global states including themes, user data, and language preferences.

jsx

import React, { createContext, useState, useContext } from 'react';

// Create a Context
const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () =>
    setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button
      onClick={toggleTheme}
      style={{
        backgroundColor: theme === 'light' ? '#eee' : '#333',
        color: theme === 'light' ? '#333' : '#eee',
      }}
    >
      Toggle Theme
    </button>
  );
}

// Usage in App
export default function App() {
  return (
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  );
}

Enter fullscreen mode Exit fullscreen mode

Using React Context, this example demonstrates a basic theme toggling setup.

Redux — Predictable State Container

Your app's state is centralized in a single store with Redux, and the process is straightforward: dispatch actions → reducers update the state → UI reflects the updated state.
A basic Redux example of the same theme toggling is as follows:
jsx

// redux/store.js
import { createStore } from 'redux';

// Actions
const TOGGLE_THEME = 'TOGGLE_THEME';

export const toggleTheme = () => ({ type: TOGGLE_THEME });

// Reducer
const initialState = { theme: 'light' };

function themeReducer(state = initialState, action) {
  switch (action.type) {
    case TOGGLE_THEME:
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    default:
      return state;
  }
}

// Create store
export const store = createStore(themeReducer);
Enter fullscreen mode Exit fullscreen mode

jsx

// App.js
import React from 'react';
import { Provider, useDispatch, useSelector } from 'react-redux';
import { store, toggleTheme } from './redux/store';

function ThemedButton() {
  const theme = useSelector((state) => state.theme);
  const dispatch = useDispatch();

  return (
    <button
      onClick={() => dispatch(toggleTheme())}
      style={{
        backgroundColor: theme === 'light' ? '#eee' : '#333',
        color: theme === 'light' ? '#333' : '#eee',
      }}
    >
      Toggle Theme
    </button>
  );
}

export default function App() {
  return (
    <Provider store={store}>
      <ThemedButton />
    </Provider>
  );
}

Enter fullscreen mode Exit fullscreen mode

Whether you're building enterprise-grade apps or experimenting with side projects, choosing the right frontend foundation can make or break your workflow.

Core Differences Explained with Code and Use Cases

Aspect React Context Redux
Purpose Lightweight sharing of static/global data Complex, scalable app-wide state management
Setup Complexity Very simple, minimal boilerplate More boilerplate and setup (actions, reducers)
State Updates Direct updates via context provider’s state Immutable updates through reducers and actions
Middleware Support None Yes — thunk, saga for async side effects
DevTools Limited Powerful Redux DevTools for state inspection
Performance Rerenders all consumers when context value changes Optimized with selectors and memoization
Learning Curve Low — easy to grasp Moderate — requires understanding of Redux flow

When to Use React Context — Detailed Scenarios with Code

Scenario 1: Theme or UI Preferences (Simple, Static Data)

React Context is often used when you want to switch between bright and dark themes.
jsx

// Same as the earlier ThemeContext example

Enter fullscreen mode Exit fullscreen mode

Scenario 2: Authentication Status (User Info)

React Context may save the user's logged-in information for easy access across the whole app:
jsx

const AuthContext = createContext();

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = (userInfo) => setUser(userInfo);
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

function UserProfile() {
  const { user, logout } = useContext(AuthContext);

  if (!user) return <div>Please log in.</div>;

  return (
    <div>
      Welcome, {user.name}
      <button onClick={logout}>Logout</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

When React Context Becomes Problematic

  • Performance issues might arise because all the components that use the context re-render whenever the state changes (for example, in a real-time feed or chat).
  • Clean code using only Context becomes more difficult to maintain as the application expands and the state logic becomes more intricate.

When to Use Redux — In-Depth with Real-World Code

Scenario : Shopping Cart with Complex Actions

The user may add, remove, update quantity, and apply discounts, among other actions. Redux stands apart in this respect.
jsx

// actions.js
export const ADD_ITEM = 'ADD_ITEM';
export const REMOVE_ITEM = 'REMOVE_ITEM';

export const addItem = (item) => ({ type: ADD_ITEM, payload: item });
export const removeItem = (id) => ({ type: REMOVE_ITEM, payload: id });

// reducer.js
const initialState = {
  cartItems: [],
};

function cartReducer(state = initialState, action) {
  switch (action.type) {
    case ADD_ITEM:
      return { ...state, cartItems: [...state.cartItems, action.payload] };
    case REMOVE_ITEM:
      return {
        ...state,
        cartItems: state.cartItems.filter((item) => item.id !== action.payload),
      };
    default:
      return state;
  }
}

export default cartReducer;

Enter fullscreen mode Exit fullscreen mode

Using Redux Toolkit (Best Practice)

Redux Toolkit significantly reduces boilerplate complexity:
jsx

import { configureStore, createSlice } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload);
    },
    removeItem: (state, action) => {
      state.items = state.items.filter(item => item.id !== action.payload);
    },
  },
});

export const { addItem, removeItem } = cartSlice.actions;

const store = configureStore({ reducer: { cart: cartSlice.reducer } });

export default store;
Enter fullscreen mode Exit fullscreen mode

This makes Redux less painful to adopt while keeping all its power.

You can also even checkout "Writing Cleaner React Components: A Practical Guide"

Performance — The Devil’s in the Details

React Context Performance Gotchas

Every time the value of the context provider changes, all components that use that context re-render, even if they don't use the changed piece.

How to mitigate?

  • Separate your context into smaller, easier-to-manage categories, such as discrete theme context and user context.
  • useMemo to help you remember context values.
  • When working with components that consume context, use React.memo. Example: jsx
const value = React.useMemo(() => ({ theme, toggleTheme }), [theme]);
Enter fullscreen mode Exit fullscreen mode

Redux Performance Advantages

  • Selective updates are made possible via pure reducers, which process state changes.
  • Reselect and other selectors can be used to memoize derived data and prevent unnecessary re-renders.
  • Middleware handles asynchronous functions cleanly, with little impact on UI speed.

The Hybrid Approach — Using Both in One App

React Context is commonly used for UI-related state (theme, locale) in large projects, while Redux is commonly used for business logic state (cart, user data, API answers).

Final Decision Flow

Question Use React Context Use Redux
Is your app small or medium with simple global state? ✅ Yes ❌ No
Do you need complex state transitions or async middleware? ❌ No ✅ Yes
Do you want a quick setup without extra dependencies? ✅ Yes ❌ No
Is predictable state debugging important? ❌ No ✅ Yes
Does your app have frequent or complex updates? ❌ No ✅ Yes

Bonus: What About Other Alternatives?

  • Modern state management libraries with varying trade-offs include Zustand, MobX, and Recoil.
  • Consider team experience, performance requirements, and project complexity while evaluating.

Conclusion

Redux and React Context both play a role in contemporary React applications. React Context is beautiful and easy if your state is global, basic, and seldom changes.

If you need advanced tools, middleware, and a complex app state, Redux remains the industry standard.

Remember: Avoid over-engineering. React Context is a good place to start, but if your project grows, move on to Redux or other solutions.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.