Reputation: 1938
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?
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>
}
Upvotes: 12
Views: 25252
Reputation: 16239
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>
);
}
Upvotes: 22
Reputation: 3726
In addition to Ahmed Sbai's answer,
If you want to pass props from child components to server components, you can't.
For example, you might think the following will work as I did:
The following does not work:
// # page.tsx
<ClientComponent MyServerComponent={MyServerComponent} />
// # ClientComponent.tsx
function ClientComponent({ MyServerComponent }) {
return (
<div>
<MyServerComponent myExampleProp={123} />
</div>
);
}
Full example on Code Sandbox click here
Instead, you pass the props before serving the component to the client, i.e.: inside the parent server component, you put the props right away:
// # page.tsx
<ClientComponent myServerComponent={<MyServerComponent myExampleProp={123} />} />
// # ClientComponent.tsx
function ClientComponent({ myServerComponent }) {
return (
<div>
{myServerComponent}
</div>
);
}
In summary, when you want to pass data from a child component to a server component, then you're in a wrong situation.
You have to think twice.
You have the following solutions:
A. What you really want is to make an AJAX call.
B. What you really want is to move that logic to the parent server component (ex: page.tsx).
Inside ClientComponent.tsx:
// ClientComponent.tsx
// This is not allowed: ❌
import ServerComponent from './ServerComponent';
export default function ClientComponent() {
return <ServerComponent />;
}
Instead, you do this:
Inside page.tsx
// page.tsx
// This is allowed ✅
import ClientComponent from "./ClientComponent";
import ServerComponent from "./ServerComponent";
export default function Page() {
return (
<ClientComponent>
<ServerComponent propA={123} />
</ClientComponent>
);
}
Or you do this:
// page.tsx
// This is allowed ✅
import ClientComponent from "./ClientComponent";
import ServerComponent from "./ServerComponent";
export default function Page() {
return (
<ClientComponent serverComponent={<ServerComponent propA={123}/>} />
);
}
Upvotes: 2
Reputation: 5
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.
Upvotes: -3
Reputation: 71
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>
);
}
Upvotes: 1