1

I am experimenting with Next.js 13 app directory and React 18's Server Components.
Following the documentation I put an async fetch() call inside a Server Component and I mark the component as async.

But then when I place it as a child of a Client Component, it fails with error

Objects are not valid as a React child (found: [object Promise])

If I place it as a child of another Server Component, it works as intended.

Why?

enter image description here

Code here:

### page.tsx
import { ClientComp } from './ClientComp'

export default function Page() {
  return <ClientComp />
}

### ClientComp.tsx
"use client";
import { ServerComp } from "./ServerComp";

export function ClientComp() {
  {/* @ts-expect-error Async Server Component */}
  return <ServerComp />
}

### ServerComp.tsx
export async function ServerComp() {
  return <h2>ServerComp</h2>
}
Ahmed Sbai
  • 10,695
  • 9
  • 19
  • 38
Cla
  • 1,810
  • 3
  • 22
  • 36
  • 1
    Post code. It sounds like you're marking your `list.ts` component function with `async`. You can't do that. React components must be computed synchronously. – Slava Knyazev Mar 07 '23 at 18:28
  • You can inside the app directory with Next.js 13. Here some the docs https://beta.nextjs.org/docs/data-fetching/fetching#asyncawait-in-server-components – Cla Mar 07 '23 at 18:31

3 Answers3

2

Importing Server Components into Client Components

Server and Client Components can be interleaved in the same component tree. Behind the scenes, React will merge the work of both environments.
However, in React, there's a restriction around importing Server Components inside Client Components because Server Components might have server-only code (e.g. database or filesystem utilities).
For example, importing a Server Component in a Client Component will not work

so you cannot do this:

import ServerComponent from './ServerComponent';

export default function ClientComponent() {
  return (
    <>
      <ServerComponent />
    </>
  );
}

Instead, you can pass a Server Component as a child or prop of a Client Component. You can do this by wrapping both components in another Server Component. For example:

// ✅ This pattern works. You can pass a Server Component
// as a child or prop of a Client Component.
import ClientComponent from "./ClientComponent";
import ServerComponent from "./ServerComponent";

// Pages are Server Components by default
export default function Page() {
  return (
    <ClientComponent>
      <ServerComponent />
    </ClientComponent>
  );
}
Ahmed Sbai
  • 10,695
  • 9
  • 19
  • 38
  • Except if I want to pass a piece of state or function created inside the Client component to the Server component, I cannot do it using `{children}` pattern. I am creating a client component with `useState()` and I want to pass the value and setter to the child server component. So the server component can make an api request using value as param. – Cla Mar 09 '23 at 09:19
  • I don't see why you want to do that and better is to use a client component instead but I think it's not even possible – Ahmed Sbai Mar 13 '23 at 19:34
  • having the same issue -- @Cla, did you ever figure out a workaround? – Colin Ricardo Jun 21 '23 at 18:14
  • Yes, I figured it out in the end. When you want to trigger a re-rendering of a server component (with its eventual networking call), you cannot do it directly from the client component. But if your client component updates the URL, adding or changing a query param for example, the server component will re-render. This way you can achieve the same result. Then of course there are nuances of what you can and cannot do with this strategy – Cla Jun 22 '23 at 12:32
0

I was facing the same issue, I wanted to pass props to the server component, but its impossible to change the state of the server component.

What you can do is to put all your state in a client component, then place the server component in server one and wrap it with the client one with the state...

for example:

'use client'

export default function MenuButton({ children }: Props) { /* Client Component with state management */
  const [open, setOpen] = useState(false);
  return (
    <div className={styles.container}>
      <Button action={() => setOpen(!open)}>
        <HiOutlineBars3BottomLeft />
      </Button>
      <div
        className={`${styles.desktopMenu} ${
          open ? styles.active : styles.inactive
        }`}
      >
        {children}
      </div>
    </div>
  );
}



export default function Navbar() { /* Server component */
  return (
   <header>
      <MenuButton> /* Client component with all state management */
          <DesktopMenu /> /* Async server component */
      </MenuButton>    
   </header>
         
 );
}
Dom V
  • 51
  • 7
  • I see what toy are saying. But my specific need was to pass state to the innermost server component as a prop. But I get it’s not really possible. At least yet – Cla Mar 12 '23 at 09:27
0

To use an async Server Component with TypeScript, ensure you are using TypeScript 5.1.3 or higher and @types/react 18.2.8 or higher.

https://nextjs.org/docs/app/building-your-application/configuring/typescript#passing-data-between-server--client-components