getSingle on client side - get prop types for Prismic data

Hi. I am using NextJs 14 with app router. I have set up prismic in my project and following this tutorial to add a navigation menu.

Here is my main layout.tsx file:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={`${lato.variable}`}>
      <body>
        <LandingHeader />
        <ThemeProvider attribute="class">{children}</ThemeProvider>
      </body>
    </html>
  );
}

and inside my LandingHeader component, I am trying to add navigation items. The thing is that my LandingHeader needs to be a client component since I have some useState to switch between a hamburger icon (mobile) to desktop full navigation menu. Here is part of this file:

const LandingHeader = async () => {
  const client = createClient();
  const settings = await client.getSingle("settings");

  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  return (
      <header>
        <nav
          className="flex items-center justify-between p-6 lg:px-8"
          aria-label="Global"
        >
          <div className="flex lg:flex-1">
            <a href="#" className="-m-1.5 p-1.5">
              <span className="sr-only">Your Company</span>
              <Link href="/" className="">
                <Image src="/logo.svg" alt="" width={325} height={46} />
              </Link>
            </a>
          </div>
          <div className="flex lg:hidden">
            <button
              type="button"
              className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700"
              onClick={() => setMobileMenuOpen(true)}
            >
              <span className="sr-only">Open main menu</span>
              <Menu className="h-6 w-6" aria-hidden="true" />
              {/* <Bars3Icon /> */}
            </button>
          </div>
          <div className="hidden lg:flex lg:gap-x-12">
            {settings.data.navigation.map((item) => (
         
              <PrismicNextLink field={item.link} key={item.label}>{item.label}</PrismicNextLink>
            ))}
          </div>
          <div className="hidden lg:flex lg:flex-1 lg:justify-end">
            <a
              href="#"
              className="text-sm font-semibold leading-6 text-gray-900"
            >
              Log in <span aria-hidden="true">&rarr;</span>
            </a>
          </div>
        </nav>
        
      </header>
  );
};

export default LandingHeader;

I need to add "use client" to this file and then I get a warning/error that it cannot be an async function. So, it appears that I cannot get the navigation data directly in this component. So, I went back to my layout and converted it into an async function to pass navigation data as prop to my LandingHeader:

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {

  const client = createClient();
  const settings = await client.getSingle("settings");
  return (
    <html lang="en" className={`${lato.variable}`}>
      <body>
        <LandingHeader settings= {settings} />
        <ThemeProvider attribute="class">{children}</ThemeProvider>
      </body>
    </html>
  );
}

My question is whether this is the best solution and if so, I am having a hard time to receive this prop on my LandingHeader component.

I tried just receiving as type of any and kind of worked (still getting some errors) but I want to know how I can get the proper type for this? This is what i have inside the prismicio-types.d.ts file:

export interface SettingsDocumentDataNavigationItem {
  link: prismic.LinkField;
  label: prismic.KeyTextField;
}

/**
 * Content for Settings documents
 */
interface SettingsDocumentData {
  site_title: prismic.KeyTextField;
  meta_description: prismic.KeyTextField;
  og_image: prismic.ImageField<never>;
  navigation: prismic.GroupField<Simplify<SettingsDocumentDataNavigationItem>>;
}

Hi @akbarbakhshiwebdev. You should be able to get the type you need to pass your settings document to a child component. It would look something like this:

import { Content } from '@prismicio/client';

const LandingHeader = async ({
  settings,
}: {
  settings: Client.SettingsDocument;
}) => {
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

  return (
      <header>
        <nav
          className="flex items-center justify-between p-6 lg:px-8"
          aria-label="Global"
        >
        ...
        </nav>
      </header>
  );
};

export default LandingHeader;
1 Like

Thank you @Levi. I was able to use your suggestion and make it work with some minor changes.

SettingsDocument needs to be imported from the prismicio-types and then I could use that as the type for the settings prop:

const LandingHeader = async ({
  settings,
}: {
  settings: SettingsDocument;
})
1 Like