Using React Context in NextJS Server Components
Browny
Posted on May 24, 2023
Most React applications rely on context to share data between components, either directly via createContext, or indirectly via provider components from third-party libraries.
In NextJs, context is fully supported within client components but not server components. This is because, server components are not interactive or have no React state, and context is basically for rendering interactive components after some React state change.
So how can we wrap our server components in a React Context?
The latest version of NextJS replaces the pages
folder with the new app
directory which by default is the directory for server components.
You can check the migration guide see how you can replace your existing pages
directory with the new app
directory.
You can also adopt both methods to suit your needs but would recommend you read and understand the migration guide.
Using custom context provider
The following code show how you can use React context in NextJS client component.
/components/sidebar.tsx
'use client';
import { createContext, useContext, useState } from 'react';
const SidebarContext = createContext();
export function Sidebar() {
const [isOpen, setIsOpen] = useState();
return (
<SidebarContext.Provider value={{ isOpen }}>
<SidebarNav />
</SidebarContext.Provider>
);
}
function SidebarNav() {
let { isOpen } = useContext(SidebarContext);
return (
<div>
<p>Home</p>
{isOpen && <Subnav />}
</div>
);
}
However, context providers are typically rendered near the root of an application to share global states, like the current theme. Since context is not supported in Server Components, trying to create a context at the root of your application will cause an error:
/app/layout.tsx
import { createContext } from 'react';
// createContext is not supported in Server Components
export const ThemeContext = createContext({});
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
</body>
</html>
);
}
So how can we have our context provider in the server component?
We can do that by creating our context in a client component first.
Create a /providers/index.tsx
file and add your context code into it. Mark the component as client component with the use client
directive.
/providers/index.tsx
"use client"
import React, {createContext} from "react";
const ThemeContext = createContext({})
export default function ThemeProvider({children}){
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
)
}
Now we can use our provider in the server component and it will render the provider directly since it has been marked as a client component. With that done, all other client components in our will be able to consume our theme context.
import ThemeProvider from './providers';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
Using Third-Party Context Providers
Third-party packages often include Providers that need to be rendered near the root of your application. However, since Server Components are so new, many third-party providers won't have added the "use client" directive yet.
If these providers include the "use client" directive, they can be rendered directly inside of your Server Components.
If you try to render a third-party provider that doesn't have "use client", it will cause an error:
import { ThemeProvider } from 'theme-package';
export default function RootLayout({ children }) {
return (
<html>
<body>
{/* Error: `createContext` can't be used in Server Components */}
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
To solve this, we need to wrap the third-party provider in our own component and mark it as client with the use client
directive just as our custom context.
Edit your custom context as below.
/providers/index.tsx
"use client"
import React from "react";
import { ThemeProvider } from 'theme-package';
import { AuthProvider } from 'auth-package';
export default function MyAppProvider({children}){
return (
<ThemeProvider>
<AuthProvider>
{children}
</AuthProvider>
</ThemeProvider>
)
}
Now we can render the MyAppProvider
directly in our root layout.
import { MyAppProvider } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<MyAppProvider>{children}</MyAppProvider>
</body>
</html>
);
}
Your app is now set to consume your custom providers as well as third-party providers and with the provider rendered at the root, all the hooks and components from these libraries will work as expected.
Important
You should render providers as deep as possible in the components tree. This makes it easier for Next.js to optimize the static parts of your Server Components.
Conclusion
React Context Provide developers a way to share data across our app. Client components in React by default supports context.
We can use this feature in server components only by marking our provider as client.
React context should be rendered as deep as possible in the components tree.
Posted on May 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.