DEV Community

Cover image for Managing Global State with useReducer and Context API in Next JS 14
Muhammad Azfar Aslam
Muhammad Azfar Aslam

Posted on • Edited on

Managing Global State with useReducer and Context API in Next JS 14

Salut,
I've been a big fan of Next JS since the beginning but since Next JS 13, many things changed compared to Next JS 12. One of the main differences is Redux and Global State. Managing a global state can be a challenging task, especially when the state needs to be shared across multiple components. One reason is that Redux is only available on the client side. The question is how can we manage the global state on the server side? I came up with the solution context API. However, if we have to manage complex logic, the combination of useReducer and the Context API provides an elegant solution to this problem. In this tutorial, we will create a simple counter application to demonstrate how to use useReducer and the Context API to manage the global state in React. Let's dive in to the example.

Step 1: Setting Up the Context and Reducer

First, let's create a new context for our counter application and define a reducer function that will handle state updates:

"use client";
import React, { createContext, useContext, useReducer } from 'react';

// Create a new context for the counter
const CounterContext = createContext();

// Reducer function
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
};

Enter fullscreen mode Exit fullscreen mode

In this code, we define a new context called CounterContext using React's createContext function. We also define a reducer function that takes the current state and an action, and returns the new state based on the action type.

Step 2: Creating the Counter Provider

Next, let's create a CounterProvider component that will use the useReducer hook to manage the state and provide the state and dispatch function to its children:

// Counter provider component
const CounterProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      {children}
    </CounterContext.Provider>
  );
};

Enter fullscreen mode Exit fullscreen mode

In this code, we use the useReducer hook to create a state and dispatch function pair. We then use the CounterContext.Provider component to wrap the children components, providing them with the state and dispatch function via the context.

Step 3: Consuming the Counter Context

Now, let's create a custom hook called useCounter that will allow us to consume the counter context in our components:

// Custom hook to consume the CounterContext
const useCounter = () => {
  const context = useContext(CounterContext);
  if (!context) {
    throw new Error('useCounter must be used within a CounterProvider');
  }
  return context;
};

Enter fullscreen mode Exit fullscreen mode

In this code, we use React's useContext hook to access the counter context. We also perform a check to ensure that the hook is used within a CounterProvider component, throwing an error if it is not.

Step 4: Creating Components Using the Counter Context

Finally, let's create two components, CounterDisplay and CounterControls, that will use the useCounter hook to interact with the counter state:

const CounterDisplay = () => {
  const { state } = useCounter();

  return <div>Count: {state.count}</div>;
};

const CounterControls = () => {
  const { dispatch } = useCounter();

  return (
    <div>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

In these components, we use the useCounter hook to access the counter state and dispatch function. The CounterDisplay component displays the current count, while the CounterControls component provides buttons to increment and decrement the count.

Step 5: Using the Counter Provider in the App

Finally, let's use the CounterProvider component in our App component to wrap the CounterDisplay and CounterControls components:

const HomePageWrapper = () => {
  return (
    <CounterProvider>
      <CounterDisplay />
      <CounterControls />
    </CounterProvider>
  );
};

Enter fullscreen mode Exit fullscreen mode

In this code, we wrap the CounterDisplay and CounterControls components with the CounterProvider component to provide them with access to the counter state and dispatch function.

Conclusion

In this tutorial, we have learned how to use useReducer and the Context API to manage the global state in a Next JS 14 application. By using these tools, we can create a centralized state management system that makes it easy to share state across multiple components.

I hope this will be helpful, love to connect with you on LinkedIn


Few more articles

Top comments (3)

Collapse
 
ricardogesteves profile image
Ricardo Esteves

Why not just use Zustand? It's more scalable, batter performance(less rerenders) and really easy to implement.

Collapse
 
muhammadazfaraslam profile image
Muhammad Azfar Aslam

Thank you @ricardogesteves for the alternative but I prefer to install minimum packages, also context API is something everyone is familiar with.

Collapse
 
ricardogesteves profile image
Ricardo Esteves

👍