next.jstailwind-uiheadless-ui

Headless UI Disclosure render prop error: "Functions are not valid as a child of Client Components."


I tried to use a Navbar from the Tailwind component library, but there's something wrong with the way I'm using Headless UI. I'm following https://headlessui.com/react/disclosure and I can't figure out how to implement the render prop example. I have a Next.js project and two main files, page.tsx and Example.tsx

//src/app/page.tsx
import React from "react";
import Example from "@/components/ui/Example";

export default function Home() {
  return (
    <main>
      <Example />
    </main>
  );
}
//src/ui/Example.tsx
import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/react";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import clsx from "clsx";

export default function Example() {
  return (
    <Disclosure>
      {({ open }) => (
        <>
          <DisclosureButton className="flex items-center gap-2">
            Do you offer technical support?
            <ChevronDownIcon className={clsx("w-5", open && "rotate-180")} />
          </DisclosureButton>
          <DisclosurePanel>No</DisclosurePanel>
        </>
      )}
    </Disclosure>
  );
}

I am getting:

Error: Functions are not valid as a child of Client Components. This may happen if you return children instead of from render. Or maybe you meant to call this function rather than return it. <... children={function children}>

The problem seems to be the {({open}) => ...} function. I need to turn it into a valid component, but I don't know how.


Solution

  • In NextJS, all components are Server Components by default.

    Disclosure needs to be a Client component because it has to be interactive (show/hide) and may be using some hooks (which is only available in client components)

    // add "use client" directive
    "use client"
    
    import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/react";
    import { ChevronDownIcon } from "@heroicons/react/20/solid";
    import clsx from "clsx";
    
    export default function Example() {
      return (
        <Disclosure>
          {({ open }) => (
            <>
              <DisclosureButton className="flex items-center gap-2">
                Do you offer technical support?
                <ChevronDownIcon className={clsx("w-5", open && "rotate-180")} />
              </DisclosureButton>
              <DisclosurePanel>No</DisclosurePanel>
            </>
          )}
        </Disclosure>
      );
    }