According to this RFC:
Client Components May not import Server Components or call server hooks/utilities, because those only work on the server.
I am working on a project using react 18.2 and nextjs v14 with app router.
I have this page which I am working on:
The page, is a client component, because almost everything in the page depends on client features.
This is a form, so everything depends on the React useState()
hook.
The actual code of the page.tsx looks as follows, it's a big component, I am using a components library called JoyUI (by mui).
The important part is the very first line in the code where I am using the "use client"
directive since, as I've mentioned, this component (NewModeratorPage) depends on many features that are client side features.
The second part to look at is the <SelectCompany />
component which is in the return block of the <NewModeratorPage />
component as shown below:
'use client'
import React from 'react'
import { Grid } from '@mui/material'
import useBreakpoints from '@/hooks/useBreakpoints'
import PasswordInput from '@/components/PasswordInput'
import AvatarUploader from '@/components/AvatarUploader'
import { Stack, Input, FormLabel, Checkbox } from '@mui/joy'
import {
EmailRounded as EmailIcon,
PhoneRounded as PhoneIcon,
PersonRounded as PersonIcon,
} from '@mui/icons-material'
import SelectCompany from './SelectCompany'
const NewModeratorPage = () => {
const { isDownMd } = useBreakpoints()
return (
<Grid container spacing={2}>
<Grid item xs={12} md={3}>
<AvatarUploader placeholder={<PersonIcon sx={{ fontSize: 64 }} />} />
</Grid>
<Grid item xs={12} md={8}>
<Stack spacing={1}>
<FormLabel>Name</FormLabel>
<Stack direction={isDownMd ? 'column' : 'row'} spacing={2}>
<Input placeholder="First name" size="sm" fullWidth required />
<Input placeholder="Last name" size="sm" fullWidth required />
</Stack>
<FormLabel>Credentials</FormLabel>
<Stack direction={isDownMd ? 'column' : 'row'} spacing={2}>
<Input
placeholder="Email"
startDecorator={<EmailIcon />}
type="email"
size="sm"
fullWidth
required
/>
<Input placeholder="Phone number" startDecorator={<PhoneIcon />} size="sm" fullWidth />
</Stack>
<Stack direction={isDownMd ? 'column' : 'row'} spacing={2}>
<PasswordInput size="sm" fullWidth required />
</Stack>
<Checkbox size="sm" label="Send password via email" defaultChecked />
<SelectCompany />
</Stack>
</Grid>
</Grid>
)
}
export default NewModeratorPage
<SelectCompany />
is a server component.
It renders a <Select>
with <Options>
fetched from an API.So, the <SelectCompany />
involves data fetching.
Which is a good indicator that this component should be a server component.
<SelectCompany/>
pre-fetched, with all the data.So no loading time for fetching the companies on the client side.
Here's the code of the <SelectCompany />
server component.
"use server"
import React, { PropsWithChildren } from 'react'
import { Select, Option, Avatar, Stack } from '@mui/joy'
export interface SelectCompanyProps extends PropsWithChildren {}
const SelectCompany = async ({ children, ...props }: SelectCompanyProps) => {
const response = await fetch('/api/companies')
const data = await response.json()
console.log(data)
return (
<Select value={1}>
<Option value={1}>
<Stack direction="row" spacing={2}>
<Avatar size="sm" />
<React.Fragment>Company 1</React.Fragment>
</Stack>
</Option>
</Select>
)
}
export default SelectCompany
Firstly, I am getting this error in the browser:
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.
And in the terminal (I am on Linux btw), I am getting this error:
Internal error: Error: Server Functions cannot be called during initial render. This would create a fetch waterfall. Try to use a Server Component to pass data to Client Components instead.
The reason of this error, as far as I can tell, is that it's not allowed for client components to import server components,
So what is the solution to my case? I want <SelectCompanies />
to be a server component.
There are a couple of supported and unsupported pattern from nextjs docs
'use client'
// You cannot import a Server Component into a Client Component.
import ServerComponent from './Server-Component'
export default function ClientComponent({
children,
}: {
children: React.ReactNode
}) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ServerComponent />
</>
)
}
This is what you tried instead you can pass the server component as a child like this:
'use client'
import { useState } from 'react'
export default function ClientComponent({
children,
}: {
children: React.ReactNode
}) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
)
}
// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ClientComponent from './client-component'
import ServerComponent from './server-component'
// Pages in Next.js are Server Components by default
export default function Page() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
)
}
By passing them as children we are aware of all server components and can render/stream them.