Search code examples
next.jsnext-authreact-server-components

Passing Server Components to Client Components as Props not working


I am getting an error when trying to pass a server component to a client component as props for a next-auth v5 in Next.js v14 implementation. I am trying to follow this pattern on the Next site https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props to pass a server component to a client component. The error is:

Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding 'use client' to a module that was originally written for the server.

What needs to be altered to resolve this error?

The 3 components are as follows:

  • Nav - client component & this is imported in the page
  • AuthClientInOutWrapper - client component
  • AuthServerInOutForms - server component

AuthClientInOutWrapper:

"use client";
import React from "react";
async function AuthClientInOutWrapper({
  children
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      {children}
    </>
  );
}
export default AuthClientInOutWrapper;

AuthServerInOutForms

"use server";
import { auth, signIn, signOut } from "auth";

async function AuthServerInOutForms() {
  const session = await auth();
  return (
    <>
      {session && session.user ? (
        <>
          <h1>sign out 44</h1>
          <form
            action={async () => {
              await signOut();
            }}
          >
            <button className="text-white" type="submit">Sign Out 44</button>
          </form>
        </>
      ) : (
        <>
          <h1>sign in 44</h1>
          <form
            action={async () => {
              await signIn();
            }}
          >
            <button className="text-white" type="submit">Sign In 44</button>
          </form>
        </>
      )}
    </>
  );
}
export default AuthServerInOutForms;

Nav client component:

<AuthClientInOutWrapper>
  <AuthServerInOutForms />
</AuthClientInOutWrapper>

Simplified Nav complete component:

'use client';

import React, { ReactNode, useState } from 'react';
import Link from 'next/link';

import AuthClientInOutWrapperCatto from '../UI-client-server/AuthClientInOutWrapperCatto/AuthClientInOutWrapperCatto';
import AuthServerInOutFormsCatto from '../UI-client-server/AuthServerInOutFormsCatto/AuthServerInOutFormsCatto';


const Nav = () => {
  const [isNavOpen, setIsNavOpen] = useState(false);

  const handleCloseAllClick = () => {
    setIsNavOpen(false);
  };
  return (
    <>
      <nav className="border-gray-200 bg-white dark:bg-gray-900">
        <div className="mx-auto flex max-w-screen-xl flex-wrap items-center justify-between p-4">
          {/* Main Nav Level-1 Section  */}
          <div>
            <ul className=" flex flex-col rounded-lg border border-gray-100 bg-gray-50 p-4 font-medium rtl:space-x-reverse dark:border-gray-700 dark:bg-gray-800 md:mt-0 md:flex-row md:space-x-8 md:border-0 md:bg-white md:p-0 md:dark:bg-gray-900">
              {/* Home Link */}
              <li>
                <Link
                  href="/"
                  onClick={handleCloseAllClick}
                >
                  Home
                </Link>
              </li>
              {/* About Link */}
              <li>
                <Link
                  href="/about"
                  onClick={handleCloseAllClick}
                >
                  <span>About</span>
                </Link>
              </li>

              <AuthClientInOutWrapperCatto>
                <AuthServerInOutFormsCatto />
              </AuthClientInOutWrapperCatto>

            </ul>
          </div>
        </div>
      </nav>
    </>
  );
};

export default Nav;

Edit: thanks for the comment from Ahmed Abdelbaset. I removed the "use server" line at the top of the server component AuthServerInOutForms & then the page renders however the sign in button is not clickable & in the console there is this error:

Uncaught (in promise) Error: Invariant: headers() expects to have requestAsyncStorage, none available.

also fyi: If the server component is placed directly in the main layout it will work as expected. The issue is when the server component is attempted to passed into the client component.


Solution

  • The <AuthServerInOutFormsCatto /> is a Server Component, yet it's being utilized within a client component (<Nav />), which is not feasible.

    The mentioned trick, clarifies that Server Components should only be employed within Server Components. To achieve the desired behavior, you can pass the children prop from a Server Component level.

    export const Nav = ({ children }) => {
      // ...
      <AuthClientInOutWrapperCatto>
        {children}
      </AuthClientInOutWrapperCatto>
    }
    

    Then in the layout.tsx (or wherever the <Nav /> is rendered) pass the <AuthServerInOutFormsCatto /> as a child.

    export default function Layout() {
      return (
        <div>
          <Nav>
            <AuthServerInOutFormsCatto />
          </Nav>
        </div>
      )
    }
    

    You don't need "use server" at the top of AuthServerInOutForms file. You should move it to the action itself

    <form
      action={async () => {
        "use server";
    
        await signIn();
      }}
    >
    

    The "use server" directive is used for Server Actions not Server Components. When marking a file with use server, Next.js will treat all exports from this file as Server Actions. This might lead to unexpected behaviors.