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.
- TypeScript still complains about the slice data that gets passed from a prop to a client component.
- 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.
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?