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.