paint-brush
Using Client Components to Display React Server Componentsby@slaymance
1,721 reads
1,721 reads

Using Client Components to Display React Server Components

by Shane LaymanceJuly 30th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Pass React Server Components to Client Components in the children prop when they all follow the same display rules. Use a custom prop to pass Server Components to Client Components when components follow different display rules or additional information is needed alongside each component.
featured image - Using Client Components to Display React Server Components
Shane Laymance HackerNoon profile picture


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:


  1. passing Server Components as children and
  2. passing Server Components as custom props to allow for more flexibility over each component.


Passing Server Components as 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.


Passing Server Components as Custom Props

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.

Conclusion

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.