DEV Community

Yuki Kasugai
Yuki Kasugai

Posted on

useContext + useReducer = powerful and flexible way to manage global state!

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?

alt text
Reference

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;
Enter fullscreen mode Exit fullscreen mode

Let's see the code one by one.

const [state, dispatch] = useReducer(reducer, initialState);
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

Reference1
Reference2

Top comments (2)

Collapse
 
trojandeveloper profile image
Vinnie Stracke

Thanks for your post. It's very wonderful!

Collapse
 
yukio1o5 profile image
Yuki Kasugai

Hi Vinnie, I'm glad to heart my post is useful for you! Thanks.