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,
useContextwill returnundefined, causing errors. - Passing only state or dispatch: You must pass both
stateanddispatchin 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
useReducerinside a provider component. - Pass both
stateanddispatchas context value. - Consume context with
useContextin 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.