How to use React Context API with Next.js 13

How to use React Context API with Next.js 13

If you want to manage states globally and eliminate the need for prop drilling in your Next.js 13 app, consider using the React Context API. This article will provide you with step-by-step instructions on how to set up and implement the React Context API in your Next.js 13 app.

In Next.js 13 all of the components by default server render, integrating client-side libraries or APIs can be tricky. But fear not, as this article will show you how to overcome this challenge and leverage the power of the React Context API in your Next.js 13 app directory.

How to set up the Next.js 13 project

you have to make sure you have the latest version of node.js like 18, or 20 then run

yarn create next-app nextjs13-context-api

or

npx create-next-app@latest nextjs13-context-api

then show a prompt like

Need to install the following packages: create-next-app@13.4.9 then press enter button.

you will have several prompts and based on your requirements select Yes or No but whenever ask Would you like to use App Router? (recommended) › No / Yes make sure you select Yes.

Create the Context Provider

In Next.js 13, server-side rendering is the default for all components. However, if we create a context using the Context API within a Server Component, it will result in an error. To fix this problem, we need to create the context and render its provider within a Client Component. To do this, we can add the use client flag at the beginning of the file.

With this in mind, let’s create a context that will allow us to increment, decrement, and reset a counter. To get started, create a folder on the top context directory and Inside this folder, create a file called counter.context.tsx and add the following code.

/context/counter.context.tsx

"use client";

import React, { Dispatch, createContext, useReducer } from "react";

type StateType = {
  count: number;
};

type ActionType = {
  type: string;
};

const initialState: StateType = {
  count: 0,
};

const reducer = (state: StateType, action: ActionType) => {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 };
    case "DECREMENT":
      return { ...state, count: state.count - 1 };
    case "RESET":
      return { ...state, count: 0 };
    default:
      return state;
  }
};

export const CounterContext = createContext<{
  state: StateType;
  dispatch: Dispatch<ActionType>;
}>({ state: initialState, dispatch: () => null });

export const CounterContextProvider = ({
  children,
}: {
  children: React.ReactNode;
}) => {
  const [state, dispatch] = useReducer(reducer, initialState);

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

Provide the Context Provider to Next.js

Now we need to ensure that the context provider is rendered at the root of the app so that all Client Components can consume it. To do this, open the layout.tsx file located in the app directory and wrap the context provider around the children node in the HTML template.

Note that even though the RootLayout component is a Server Component, it can now render the context provider since it has been marked as a Client Component with the "use client"; flag.

/app/layout.tsx

import { CounterContextProvider } from "@/context/counter.context";

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <CounterContextProvider>{children}</CounterContextProvider>
      </body>
    </html>
  );
}

It’s important to remember to render providers as deeply as possible in the component tree for optimal performance. In this case, we’re only wrapping {children} instead of the entire <html> document, which makes it easier for Next.js to optimize the static parts of your Server Components.

Use the Context API in the Next.js 13

Now that we have set up the Context Provider in the app’s root layout component, we can use it to manage the app’s state in our components. Let’s start by creating a counter component that uses the context to increase, decrease, and reset the counter.

Since the Context API only works in Client Components, we need to insert the "use client"; flag at the top of the file. To get started, navigate to the main directory and create a new folder named “components“. Within the “components” folder, create a new file named counter.component.tsx and add the following code snippets.

/components/counter.component.tsx

"use client";

import { CounterContext } from "@/context/counter.context";
import React, { useContext } from "react";

export default function Counter() {
  const { state, dispatch } = useContext(CounterContext);

  return (
    <div style={{ marginBottom: "4rem", textAlign: "center" }}>
      <h4 style={{ marginBottom: 16 }}>{state.count}</h4>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>increment</button>
      <button
        onClick={() => dispatch({ type: "DECREMENT" })}
        style={{ marginInline: 16 }}
      >
        decrement
      </button>
      <button onClick={() => dispatch({ type: "RESET" })}>reset</button>
    </div>
  );
}

Fetch and Display Data From an API

In addition to the counter component, let’s also explore the new use hook introduced in React 18. However, it’s important to note that at the time of writing this article, the use hook can cause an infinite loop, so the React team created the cache function to prevent this.

To demonstrate the use hook, we’ll fetch a list of users from the jsonplaceholder.typicode.com/users API using the cache function and the Fetch API. Then, we’ll display the list of users in the UI.

Before we make the API request, it’s essential to create a TypeScript type that represents the structure of the User document we’ll receive from the API. Therefore, we should create a types.ts file in the “/app” directory and add the necessary code to it.

/app/types.ts

export type User = {
  id: number;
  name: string;
  email: string;
};

Next, create a users.component.tsx file inside the “components” directory and add the following code.

/components/users.component.tsx

"use client";

import React, { cache, use } from "react";
import { User } from "../app/types";

const getUsers = cache(() =>
  fetch("https://jsonplaceholder.typicode.com/users").then((res) => res.json())
);

export default function ListUsers() {
  let users = use<User[]>(getUsers());

  return (
    <>
      <div
        style={{
          display: "grid",
          gridTemplateColumns: "1fr 1fr 1fr 1fr",
          gap: 20,
        }}
      >
        {users.map((user) => (
          <div
            key={user.id}
            style={{ border: "1px solid #ccc", textAlign: "center" }}
          >
            <img
              src={`https://robohash.org/${user.id}?set=set2&size=180x180`}
              alt={user.name}
              style={{ height: 180, width: 180 }}
            />
            <h3>{user.name}</h3>
          </div>
        ))}
      </div>
    </>
  );
}

Bring All the Components Into the App Directory

Next, we need to include the <Counter /> and <ListUsers /> components in the root page component so that they can be rendered. Even though the root page component is a Server Component, it can render the <Counter /> and <ListUsers /> components because they are marked as Client Components.

To achieve this, open the /app/page.tsx file and replace its contents with the following code:

/app/page.tsx

import Counter from "../components/counter.component";
import ListUsers from "../components/users.component";

export default function Home() {
  return (
    <main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
      <Counter />
      <ListUsers />
    </main>
  );
}

Congratulations, you’ve completed the app! To see it in action, start the Next.js development server and go to http://localhost:3000/. Try clicking on the buttons to increase, decrease, and reset the counter to confirm that the Context API is functioning correctly.

Did you find this article valuable?

Support Elias Soykat by becoming a sponsor. Any amount is appreciated!