0
0
ReactHow-ToBeginner · 3 min read

How to Use useReducer with useContext in React

Use useReducer to manage complex state logic and useContext to share that state and dispatch function across components. Create a context, provide the reducer state and dispatch via a context provider, then consume them in child components with useContext.
📐

Syntax

To use useReducer with useContext, you first create a context with React.createContext(). Then, inside a provider component, initialize state with useReducer and pass both state and dispatch as the context value. Child components use useContext to access the shared state and dispatch actions.

  • Reducer function: Defines how state updates based on actions.
  • useReducer: Returns current state and dispatch function.
  • Context Provider: Shares state and dispatch to children.
  • useContext: Accesses shared state and dispatch in any component.
javascript
import React, { createContext, useReducer, useContext } from 'react';

// 1. Create context
const MyContext = createContext(null);

// 2. Define reducer function
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

// 3. Create provider component
function MyProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <MyContext.Provider value={{ state, dispatch }}>
      {children}
    </MyContext.Provider>
  );
}

// 4. Consume context in child
function Counter() {
  const { state, dispatch } = useContext(MyContext);
  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}
💻

Example

This example shows a simple counter app using useReducer for state logic and useContext to share the state and dispatch across components. The CountProvider wraps the app, and the Counter component updates the count by dispatching actions.

javascript
import React, { createContext, useReducer, useContext } from 'react';
import { createRoot } from 'react-dom/client';

const CountContext = createContext(null);

function countReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function CountProvider({ children }) {
  const [state, dispatch] = useReducer(countReducer, { count: 0 });
  return (
    <CountContext.Provider value={{ state, dispatch }}>
      {children}
    </CountContext.Provider>
  );
}

function Counter() {
  const { state, dispatch } = useContext(CountContext);
  return (
    <div style={{ fontFamily: 'Arial', textAlign: 'center', marginTop: '2rem' }}>
      <h2>Count: {state.count}</h2>
      <button onClick={() => dispatch({ type: 'increment' })} style={{ marginRight: '1rem' }}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

function App() {
  return (
    <CountProvider>
      <Counter />
    </CountProvider>
  );
}

const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
Output
Count: 0 [+] [-] buttons that increment and decrement the count on click
⚠️

Common Pitfalls

  • Not wrapping components with the Provider: If you forget to wrap your components with the context provider, useContext will return undefined, causing errors.
  • Passing only state or dispatch: You must pass both state and dispatch in the context value to allow components to read state and dispatch actions.
  • Mutating state directly: Always return new state objects in the reducer; never mutate the existing state.
  • Using multiple contexts unnecessarily: Combine related state in one reducer/context to avoid complexity.
javascript
/* Wrong: Not wrapping component with provider */
function Counter() {
  const { state, dispatch } = useContext(MyContext); // MyContext.Provider missing
  return <p>{state.count}</p>;
}

/* Right: Wrap with provider */
function App() {
  return (
    <MyProvider>
      <Counter />
    </MyProvider>
  );
}
📊

Quick Reference

Remember these key points when using useReducer with useContext:

  • Create a context with createContext().
  • Define a reducer function to handle state updates.
  • Use useReducer inside a provider component.
  • Pass both state and dispatch as context value.
  • Consume context with useContext in child components.

Key Takeaways

Combine useReducer and useContext to manage and share complex state easily.
Always wrap consuming components with the context provider.
Pass both state and dispatch in the context value for full access.
Never mutate state directly inside the reducer; always return new state.
Use useContext to access shared state and dispatch in any component.