With the advent of React Server Components, the general recommendation is to move Client Components to the leaves of your component tree where possible. However, you’ll often want to conditionally display Server Components using client-side interactivity. In this case, Server Components should be passed to Client Components as props so that the client side is responsible for determining how the Server Components are displayed.
We’re going to explore two strategies for passing Server Components as props:
children
and
children
Passing JSX as children to components is a common compositional practice in React. Server Components can be explicitly nested within Client Components from a parent Server Component. The Client Component will receive the generic children
prop which contains the server-rendered result of the Server Components passed as children. The Client Component has no knowledge of the contents of children
and is merely responsible for determining how to display the content. Here’s an example of Server Components being passed as children
to a Client Component:
// ServerList.jsx
import ClientList from './ClientList'
import ServerListItem from './ServerListItem'
import getData from './utils/getData'
export default async function ServerList() {
const data = await getData()
return (
<ClientList>
{data.map((listItem) => (
<ServerListItem key={listItem.id} message={listItem.message} />
))}
</ClientList>
)
}
// ServerListItem.jsx
export default function ServerListItem({ message }) {
return <li>{message}</li>
}
// ClientList.jsx
'use client'
import React from 'react'
export default function ClientList({ children }) {
const [showChildren, setShowChildren] = React.useState(true)
const handleClick = () => {
setShowChildren((prevState) => !prevState)
}
return (
<>
<button onClick={handleClick}>Toggle Children</button>
<ul>
{showChildren && children}
</ul>
</>
)
}
In the example above, the <ServerList>
component gets some data and maps them to a series of <ServerListItem>
components passed as children to <ClientList>
. <ClientList>
has an interactive button which, when clicked, toggles whether the contents of the children
prop are displayed on the page.
When JSX is passed as children
to a component, the component is unable to manipulate the individual components that make up its children; all children follow the same rules. However, there are many cases where you may want more control over the individual Server Components passed to the Client Component. To achieve this, Server Components should be passed as a custom prop to the Client Component, often with some additional information required for the client-side logic.
Let’s refactor our basic list example from above to introduce filtering logic based on a user’s input:
// ServerList.jsx
import ClientList from './ClientList'
import ServerListItem from './ServerListItem'
import getData from './utils/getData'
export default async function ServerList() {
const data = await getData()
const listItems = data.map((listItem) => ({
message: listItem.message,
content: <ServerListItem key={listItem.id} message={listItem.message} />,
}))
return <ClientList listItems={listItems} />
}
// ServerListItem.jsx
export default function ServerListItem({ message }) {
return <li>{message}</li>
}
// ClientList.jsx
'use client'
import React from 'react'
export default function ClientList({ listItems }) {
const [filterInput, setFilterInput] = React.useState('')
const handleChange = (e) => {
setFilterInput(e.target.value)
}
const filteredItems = listItems
.filter((listItem) => listItem.message.includes(filterInput))
.map((listItem) => listItem.content)
return (
<>
<input onChange={handleChange} />
<ul>
{filteredItems}
</ul>
</>
)
}
In this example, <ServerList>
creates an array of objects which is passed as the listItems
prop to <ClientList>
. Each object has a content
key for which the value is the server-rendered result of a <ServerListItem>
component as well as the message
which will ultimately be displayed by the <ServerListItem>
. The <ClientComponent>
has an <input>
that, when changed, updates the filterInput
state variable. This filterInput
state is then used to determine if each listItem.content
should be displayed based on whether or not the listItem.message
meets the filtering criteria.
Passing Server Components as props to Client Components is useful when the displaying of the Server Components is tied to some client-side interactivity or state. Nesting Server Components within a Client Component and using the children
prop is best when all child components adhere to the same display conditions. Creating a custom prop to pass Server Components to a Client Component is an alternative when control over individual child components or additional information alongside the components is needed. Both strategies leave the Client Component with only the responsibility to decide how its children will be placed on the page while taking advantage of the performance benefits of rendering those children on the server.