skip to Main Content

I’m trying to understand how to handle modals opening/closing with server components. Previously, with client components, I would just lift the state up to my Layout:

export default function Layout({ children }) {

const [showPopup, setShowPopup] = useState(true)

return (
<>
  <Popup showPopup={showPopup} setShowPopup={setShowPopup} />
  <div>{children}</div>
</>
)

And then show/hide my popup according to this state. However, with server components, I can’t do that anymore. To make it work, I would need to "use client" at the top to make it a client component, but then the whole app would be a client component (because that’s my root layout).

I want the root layout to be a server component to allow the app to leverage infrastructure toward better performance and overall user experience.

So how do you handle showing or hiding a modal without relying on useState and client components ? This case is so common that I’m probably missing something obvious, but I can’t find the answer.

2

Answers


  1. Using a wrapper component

    A time ago i saw somewhere in Next documentation that to use client side components in Layout you need to put the component in other file with "use client" and import it in your layout file

    Using next’s dynamic feature

    if it dont work you can try to use dynamic with ssr property but i dont know if this will work

    import dynamic from 'next/dynamic'
    
    const DynamicHeader = dynamic(() => import('../components/header'), {
      ssr: false,
    })
    
    Login or Signup to reply.
  2. You can’t have client interactivities, like opening a modal, or client-specific code like useState without having a Client Component. That has been said,
    not because Layout is marked as a Client Component, everything that’s rendered by it will be. As you can read on the doc, the children prop of a Client Component can be a Server Component:

    // app/page.js
    
    // ✅ 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>
      );
    }
    

    And in this part of the doc, they do not warn about having a Layout as a Client Component, as it’s ok:

    Layouts are Server Components by default but can be set to a Client Component.

    So since your Layout doesn’t seem to render anything else than the popup and children, you can make it a Client Component:

    "use client";
    
    export default function Layout({ children }) {
      const [showPopup, setShowPopup] = useState(true);
    
      return (
        <>
          <Popup showPopup={showPopup} setShowPopup={setShowPopup} />
          {/* children can still be a server component */}
          <div>{children}</div>
        </>
      );
    }
    

    But if you have some stuff in the layout that needs to be rendered on the server, move the popup in a client wrapper component, like so:

    "use client";
    
    import Popup from "./Popup"
    export default function PopupWrapper() {
      const [showPopup, setShowPopup] = useState(true);
    
      return <Popup showPopup={showPopup} setShowPopup={setShowPopup} />;
    }
    
    import PopupWrapper from "./PopupWrapper";
    
    export default function Layout({ children }) {
      return (
        <>
          <PopupWrapper />
          <div>{children}</div>
        </>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search