Pass Slice data to client in Nextjs 14

I am building a Tabs Top component with Slice content relationships.

So I create a client component inside a slice folder and pass slice data as a prop so I can use useRef useEffect and useState but I can't map() it and keep getting an error.

Error: Objects are not valid as a React child (found: object with keys {type, text, spans, direction}). If you meant to render a collection of children, use an array instead.

slice/Tabs/index.tsx
Also tabs in `````TabsList tabs` prop type is still error here.
``

  // const firstBtnRef = useRef();

  const client = createClient();

  const tabs = await Promise.all(
    slice.items.map((item) => {
      if (isFilled.contentRelationship(item.tab) && item.tab.uid) {
        return client.getByUID("tab", item.tab.uid);
      }
    })
  );

  // const firstBtnRef = useRef();

  return (
    <Bounded
      data-slice-type={slice.slice_type}
      data-slice-variation={slice.variation}
      className={`${styles.inner}`}
    >
      <InnerWrapper className={`${styles.innerWrapper}`}>
        {slice.primary.show_intro ? (
          <PrismicRichText
            field={slice.primary.intro}
            components={components}
          />
        ) : null}

              <TabsList tabs={tabs} />

slices/Tabs/TabsList.tsx

  data: {
    tab_label: string;
    heading: { text: string }[]; // Update the type of 'heading' to be an array of objects with a 'text' property
    description: string;
    image: string;
  };
};

type TabsListProps = {
  tabs?: Tab[]; // Update the type of 'tabs' to be an array of 'Tab' objects
};

export default function TabsList({ tabs }: TabsListProps) {
  console.log(tabs);
  return (
    <>
      {tabs &&
        tabs.map((tab, index) => {
          return <button key={index}>{tab.data.tab_label}</button>;
        })}
    </>
  );

@solution.stack99

When I've passed data to client components that are prismic custom types, I've relied upon the auto-generated types from prismic-types. While my example isn't slice data, I believe it should operate the same way. Allow me to illustrate/explain.

I have a Header component that is a server component. Header.tsx

import { createClient } from '@/prismicio'
import Link from 'next/link'
import Section from '@/components/Section'
import Logo from '@/components/Logo'
import MobileMenu from './MobileMenu'
import DesktopMenu from './DesktopMenu'

export default async function Header() {
  const client = createClient()
  const settings = await client.getSingle('settings') // here I fetch data from a custom type
  const { navigation } = settings.data // Here I grab the repeatable group from this custom type which is an array of objects "items"
  return (
    <Section
      as="header"
      width="lg"
      className="shadow-accent justify-start py-4 shadow-sm md:py-4 lg:py-6"
    >
      <div className="flex items-center justify-between">
        <Link href="/">
          <Logo className="text-primary h-[50px] lg:h-[113px]" />
          <span className="sr-only">Return to Homepage</span>
        </Link>
        <MobileMenu navigation={navigation} />
        {navigation.length && (
          <nav className="hidden text-xl lg:block">
            <ul className="flex gap-x-12">
{/* Below I pass the navigation array to a client component */}
              <DesktopMenu navigation={navigation} />
            </ul>
          </nav>
        )}
      </div>
    </Section>
  )
}

Here is my client component DesktopMenu.tsx

'use client'
import { usePathname } from 'next/navigation'
import { SettingsDocumentDataNavigationItem } from '../../prismicio-types'
import { Button } from '@/components/ui/button'
import { PrismicNextLink } from '@prismicio/next'
import { cn } from '@/lib/utils'

// here is where I lean on the auto-generated types to type my navigation prop
type DesktopMenuProps = {
  navigation: Array<SettingsDocumentDataNavigationItem>
}

const DesktopMenu = (navigation: DesktopMenuProps): JSX.Element | null => {
  const pathname = usePathname()

  return (
    <>
      {navigation.navigation.map(({ label, link }) => {
        if ('url' in link) {
          return (
            <li key={label}>
              <Button
                variant="ghost"
                asChild
                className={cn('text-xl', {
                  'bg-accent/20': link.url === pathname,
                })}
              >
                <PrismicNextLink field={link}>{label}</PrismicNextLink>
              </Button>
            </li>
          )
        }
        return null
      })}
    </>
  )
}

export default DesktopMenu

I hope this helps some.

Thank you for the reply but I still don’t understand.

How do you get the auto-generated types from prismic-types.?

Take a look at line 3 of my DesktopMenu.tsx file. I’m able to import types that way.

Can it be any names? In your case it was SettingsDocumentDataNavigationItem.

No, they’re specific names. VS Code autocompletes them thanks to intellisense as I begin typing the name of a custom type. Are you using VS Code?

Yes, I use VS Code.

I stuck with this component for a while.

Here is a Slice with a content relationship object from Promise.all

``{
[next]   id: 'ZcEfGxEAAJlzoAwh',
[next]   type: 'tab',
[next]   tags: [],
[next]   lang: 'en-us',
[next]   slug: '-',
[next]   first_publication_date: '2024-02-05T17:47:24+0000',
[next]   last_publication_date: '2024-02-05T21:02:17+0000',
[next]   uid: 'vse-system',
[next]   link_type: 'Document',
[next]   isBroken: false
[next] }
[next] {
[next]   id: 'ZcFJGBEAADGKoFSf',
[next]   type: 'tab',
[next]   tags: [],
[next]   lang: 'en-us',
[next]   slug: '-',
[next]   first_publication_date: '2024-02-05T20:50:19+0000',
[next]   last_publication_date: '2024-02-05T21:02:43+0000',
[next]   uid: 'vse-onsite-dashboard--for-venues',
[next]   link_type: 'Document',
[next]   isBroken: false
[next] }
[next] {
[next]   id: 'ZcFKExEAABmLoFZ2',
[next]   type: 'tab',
[next]   tags: [],
[next]   lang: 'en-us',
[next]   slug: '-',
[next]   first_publication_date: '2024-02-05T20:51:27+0000',
[next]   last_publication_date: '2024-02-05T21:03:22+0000',
[next]   uid: 'vse-offsite-dashboard-for-engineers',
[next]   link_type: 'Document',
[next]   isBroken: false
[next] }

I was able to render the data into the browser but there are two issues here.

  1. TypeScript still complains about the slice data that gets passed from a prop to a client component.
  2. Unorderlist, span, bold, or any special formats from Richtext didn't render. They are rendered in a p tag.

I am not sure if these two issues need to be posted in different posts. Otherwise, please help with these issues. Thank you in advance!

slice/Tabs/index.tsx

const Tabbers = async ({ slice }: TabsProps): Promise<JSX.Element> => {

  const client = createClient();

  const tabs = await Promise.all(
    slice.items.map((item) => {
      if (isFilled.contentRelationship(item.tab) && item.tab.uid) {
        return client.getByUID("tab", item.tab.uid);
      }
    })
  );

  const tabData = tabs.map((tab) => tab.data);

  return (
    <Bounded
      data-slice-type={slice.slice_type}
      data-slice-variation={slice.variation}
      className={`${styles.inner}`}
    >
      <InnerWrapper className={`${styles.innerWrapper}`}>
        {slice.primary.show_intro ? (
          <PrismicRichText
            field={slice.primary.intro}
            components={components}
          />
        ) : null}

        <TabsList tabData={tabData} />

slice/Tabs/TabsList.tsx

"use client";

import { Content } from "@prismicio/client";
import {
  TabsSliceDefaultItem,
  TabsSliceDefaultPrimary,
} from "../../../prismicio-types";
import { Tabs, Tab, Card, CardBody } from "@nextui-org/react";

type TabsListProps = {
  tabs: Array<TabsSliceDefaultItem>;
};

export default function TabsList({ tabData }: TabsListProps) {
  console.log(tabData);
  return (
    <>
      <div className="flex w-full flex-col flex-center justify-center">
        <Tabs
          aria-label="Dynamic tabs"
          items={tabData}
          className="flex flex-row flex-center justify-center"
        >
          {tabData &&
            tabData.map((item, index) => {
              const heading = item.heading;
              const description = item.description;
              const image = item.image;
              return (
                <Tab key={index} title={item.tab_name}>
                  <Card>
                    <CardBody>
                    ...
                    ...
                    ...

Can I assume that

const tabData // is an array of your custom type called "tab?"

Let's assume that's a "yes."

You then pass an array of tab types to the TabList component.

I would think your TabList component might be structured like this (note the change in types imported):

"use client";

import { Content } from "@prismicio/client";
import {PrismicRichText} from '@prismicio/client';
import {
  TabDocument
} from "../../../prismicio-types";
import { Tabs, Tab, Card, CardBody } from "@nextui-org/react";

type TabsListProps = {
  tabs: Array<TabDocument>;
};
const TabList = (tabData: TabListProps) => {
  return (
    <div className="flex w-full flex-col flex-center justify-center">
      <Tabs>
        {
          tabData.length && tabData.map((item, index) => {
            <Tab key={index} title={item.tab_name}>
              {/* Render the richtext as follows*/}
             {'heading' in item ? (<PrismicRichText field={item.heading} />) : null }
            </Tab>
          }
        }
      </Tabs>
    </div>
  )
}
export default TabList

const tabData is an array but the Richtext is in description, heading and tab_label

I tried your suggestion but it didn't work.

[
[next]   {
[next]     tab_name: 'VSE System',
[next]     tab_label: [ [Object] ],
[next]     heading: [ [Object] ],
[next]     description: [
[next]       [Object], [Object],
[next]       [Object], [Object],
[next]       [Object], [Object],
[next]       [Object]
[next]     ],
[next]     show_primary_button: true,
[next]     primary_button_text: 'Buy Now',
[next]     primary_button_link: { link_type: 'Web', url: 'https://www.google.com/', target: '' },
[next]     show_secondary_button: true,
[next]     secondary_button_text: 'Learn More',
[next]     secondary_button_link: { link_type: 'Web', url: 'https://www.google.com/', target: '' },
[next]     image: {
[next]       dimensions: [Object],
[next]       alt: 'VSE System',
[next]       copyright: null,
[next]       url: 'https://images.prismic.io/vsewebsite/65bba502615e73009ec431bf_HouseOfWorship.png?auto=format,compress',
[next]       id: 'ZbulAmFecwCexDG_',
[next]       edit: [Object]
[next]     }
[next]   },
           {
            ...
            ...
            ...
            },
]

The object you're showing me has the shape I'd expect. In particular, notice that your tab_label, heading, and description are all arrays of [Object]? This is how Rich Text fields are returned and why we use:

<PrismicRichText field={description} />
// or...
<PrismicRichText field={heading} />

Based on my experience, I'd wager that your description has 7 HTML elements.

It worked! I was overthinking about this.

However, TypeScript still complains about tabData which is coming from the server.

`slices/Tabs/index.tsx

<TabsList tabData={tabData} />

I tried adding <TabDocument> as you suggested in the client component but it still doesn't work.

```slices/Tabs/TabsList.tsx`

`

type TabsListProps = {
  tabs: Array<TabDocument>;
};

export default function TabsList({ tabData }: TabsListProps) {
  return (```

Try:

// note that tabData is a required prop for TabsList
type TabsListProps = {
  tabData: Array<TabDocument>;
};

// note the removal of the destructuring
export default function TabsList( tabData : TabsListProps) {
  return (
...

I tried that but it crashed and there were other TY issues.

Should that be:

tabData.tabs.map


It wasn't.

I updated type TabsListProps from tabs to tabData. However, looks like whatever passes on here didn't work.

type TabsListProps = {
  tabData: Array<TabDocument>;
}

Here’s what I’m seeing:

// you’re mapping through your tabs and outputting an array of tab.data objects
const tabData = tabs.map((tab) => tab.data);

You’re then taking tabData and passing it as a prop to your TabsList component

<TabsList tabData={tabData} />

tabData is an array of data objects. It should not be destructured in your component, but we should update the type to TabDocumentData

// note the type change below
type TabsListProps = {
  tabData: Array<TabDocumentData>;
};


export default function TabsList( tabData : TabsListProps) {
// console.log(tabData) you should get an array of TabDocumentData objects
  return (
…

Update: I was incorrect about destructuring. I have provided my solution in a post below.

It resolves TypeScript in a client component but TS still complains in a slice server component.

In a client component tabData needs to wrap in {...}

slice/Tabs/TabsList.tsx
`

export default function TabsList({ tabData }: TabsListProps) {
  // console.log(tabData);
  return (

Would you be willing to share a console.log of tabData when destructured as you have it, and then again if you don't destructure it?