Using React Context in NextJS Server Components

codingbrowny

Browny

Posted on May 24, 2023

Using React Context in NextJS Server Components

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

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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>
)
}

Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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

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>
)
}

Enter fullscreen mode Exit fullscreen mode

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

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.

💖 💪 🙅 🚩
codingbrowny
Browny

Posted on May 24, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related