Have you ever used useContext and useReducer together in a React application? When I created a todo app, I initially used useContext, but I ended up double controlling the same state as both props and global state, which wasn't practical. So, I decided to refactor my code and started using useContext and useReducer instead. These hooks provide a powerful and flexible way to manage global state in a React application, without the need for prop drilling or duplicating state management logic so I will explain them.
What is useContext?
useContext is hook in React that allows you to access the value of a context directly in a functional component.
If you are not familiar with useContext, please check my article!
What is useReducer?
useReducer is also hook in React that is used to manage state in a more complex and structured manner than the useState hook, by following the Redux pattern of state management.
In order to understand the concept of useReducer, we need to know Redux pattern of state management.
What is Redux pattern of state management?
Here, you can imagine the State as your wallet.
When you click the Deposit button, the action (which is to add $10 to your wallet) is executed, and then the action is dispatched to the Store (which is like a central repository for managing the state).
The Reducer, which is a function inside the Store, receives the action and also gets the previous State (which is the information that your wallet has $0).
The Reducer then calculates $0 + $10 and updates the State accordingly (which means you finally get $10 in your wallet).
Benefit of using useContext and useReducer together:
- Provides a way to manage global state in a React application without the need for prop drilling or a state management library like Redux.
- Allows for a more structured and organized approach to state management with the use of reducers and actions, similar to Redux.
- Makes it easy to share state and state update functions across multiple components without having to pass them down as props.
Example
Next, let's see the code which is using useContext and useReduder. I created simple count app.
App.jsx
import "./App.css";
import React, { createContext, useReducer } from "react";
import Counter from "./components/Counter";
// Create a context
export const MyContext = createContext();
// Define the reducer function
const reducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
};
// Define the initial state
const initialState = { count: 0 };
// Create a provider component
const MyContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<MyContext.Provider value={{ state, dispatch }}>
{children}
</MyContext.Provider>
);
};
function App() {
return (
<div className="App">
<MyContextProvider>
<Counter />
</MyContextProvider>
</div>
);
}
export default App;
Let's see the code one by one.
const [state, dispatch] = useReducer(reducer, initialState);
Inside MyContextProvide component, I declared useReducer
which takes two arguments: reducer
function and initialState
.
const reducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
};
const initialState = { count: 0 };
The reducer
function is responsible for handling state updates based on the action objects dispatched to it. action
is plain JavaScript objects that contain a type
property to specify the action type and an optional payload
property to carry additional data for the action. (Here, we do not declare payload
.)
The initialState
is the initial value of the state.
const MyContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<MyContext.Provider value={{ state, dispatch }}>
{children}
</MyContext.Provider>
);
};
Then MyContextProvider returns an array with two values: state
and dispatch
function. The state
is the current value of the state managed by the reducer, and the dispatch
function is used to dispatch actions to the reducer to trigger state updates.
children
is a special prop in React that represents the child elements or components nested within a parent component. It is a common pattern used in React to pass down and render child components within a parent component.
In the MyContextProvider
component, children is a prop that represents the child components that will be wrapped by the MyContextProvider. These child components will have access to the context values provided by MyContext.Provider, which includes the state and dispatch function in this case.
Counter.jsx
import React from "react";
import { useContext } from "react";
import { MyContext } from "../App";
export default function Counter() {
const { state, dispatch } = useContext(MyContext);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
</div>
);
}
useContext
hook to retrieve the state and dispatch values from the MyContext object. Two button elements with an onClick event handler that dispatches actions with a type of "INCREMENT" or "DECREMENT" when clicked. These actions are handled by a reducer in App.jsx that provides the MyContext object, and they are used to update the count value in the state managed by the context, resulting in the Counter
component displaying an updated count value when the buttons are clicked.
Conclusion
This approach can help eliminate the need for double controlling the same state as props and global state, making your codebase more efficient and easier to manage. It allows you to separate the concerns of state management from the UI components, making it easier to reason about and test your code. Overall, using useContext and useReducer together can provide a cleaner and more practical way to manage global state in your React application.
Top comments (2)
Thanks for your post. It's very wonderful!
Hi Vinnie, I'm glad to heart my post is useful for you! Thanks.