Help with access data of custom type in a relationship field in a group field

Hey everyone,

I know there are many topics about content relationship fields in groups and how to access their data using graphquery. But I still can't get it to work. Also TypeScript is always yelling because it don't know of the type properties of the custom type in the relationship field.

I have following setup:

A slice called CompanyGrid

{
  "id": "company_grid",
  "type": "SharedSlice",
  "name": "CompanyGrid",
  "description": "CompanyGrid",
  "variations": [
    {
      "id": "default",
      "name": "Default",
      "docURL": "...",
      "version": "initial",
      "description": "Default",
      "imageUrl": "",
      "primary": {
        "title": {
          "type": "Text",
          "config": {
            "label": "Title",
            "placeholder": ""
          }
        },
        "headline": {
          "type": "StructuredText",
          "config": {
            "label": "Headline",
            "placeholder": "",
            "allowTargetBlank": true,
            "multi": "heading2"
          }
        }
      },
      "items": {
        "company": {
          "type": "Link",
          "config": {
            "label": "Company",
            "select": "document",
            "customtypes": [
              "company"
            ]
          }
        }
      }
    }
  ]
}

A company Custom type

{
  "format": "custom",
  "id": "company",
  "label": "Company",
  "repeatable": true,
  "status": true,
  "json": {
    "Main": {
      "uid": {
        "config": {
          "label": "UID"
        },
        "type": "UID"
      },
      "name": {
        "type": "Text",
        "config": {
          "label": "Name",
          "placeholder": ""
        }
      },
      "logo": {
        "type": "Image",
        "config": {
          "label": "Logo",
          "constraint": {},
          "thumbnails": []
        }
      },
      "description": {
        "type": "StructuredText",
        "config": {
          "label": "Description",
          "placeholder": "",
          "allowTargetBlank": true,
          "multi": "paragraph,strong,em"
        }
      },
      "career_image": {
        "type": "Image",
        "config": {
          "label": "Career Image",
          "constraint": {},
          "thumbnails": []
        }
      }
    }
  }
}

I'm using Next.js App Router. This is my page.tsx with the path 'app/[lang]/[uid]/page.tsx'

import { Metadata } from 'next'
import { notFound } from 'next/navigation'

import { SliceZone } from '@prismicio/react'
import * as prismic from '@prismicio/client'

import { createClient } from '@/prismicio'
import { components } from '@/slices'
import { getLocales } from '@/utils/getLocales'
import Header from '@/app/components/Header'
import Footer from '@/app/components/Footer'

type Params = { uid: string; lang: string }

/**
 * This page renders a Prismic Document dynamically based on the URL.
 */

export default async function Page({ params }: { params: Params }) {
  const client = createClient()
  const page = await client
    .getByUID('page', params.uid, {
      lang: params.lang,
    })
    .catch(() => notFound())

  const locales = await getLocales(page, client)

  const header = await client.getSingle('header')
  const footer = await client.getSingle('footer')

  return (
    <>
      <Header data={header.data} lang={params.lang} locales={locales} />
      <SliceZone slices={page.data.slices} components={components} />
      <Footer data={footer.data} />
    </>
  )
}

export async function generateStaticParams() {
  const client = createClient()

  /**
   * Query all Documents from the API, except the homepage.
   */
  const pages = await client.getAllByType('page', {
    predicates: [prismic.filter.not('my.page.uid', 'home')],
    lang: '*',
  })

  /**
   * Define a path for every Document.
   */
  return pages.map(async (page) => {
    return { uid: page.uid, lang: page.lang }
  })
}

And this my CompanyGrid Slice

'use client'

import Section from '@/app/components/Section'
import SectionHeadline from '@/app/components/SectionHeadline'
import { Content } from '@prismicio/client'
import { SliceComponentProps } from '@prismicio/react'
import { useEffect } from 'react'
import { Container, Grid } from 'theme-ui'
import { CompanyGridSliceDefaultItem } from '../../../prismicio-types'

/**
 * Props for `CompanyGrid`.
 */
export type CompanyGridProps = SliceComponentProps<Content.CompanyGridSlice>

/**
 * Grid item component.
 */
const GridItem = ({ item }: { item: CompanyGridSliceDefaultItem }) => {
  useEffect(() => {
    console.log('item', item)
  }, [])

  return <div>{item.company.data.name}</div>
}

/**
 * Component for "CompanyGrid" Slices.
 */
const CompanyGrid = ({ slice }: CompanyGridProps): JSX.Element => {
  return (
    <Section
      data-slice-type={slice.slice_type}
      data-slice-variation={slice.variation}
    >
      <Container>
        <SectionHeadline
          title={slice.primary.title}
          headline={slice.primary.headline}
          hasArrow
        />
        <Grid columns={[1, null, 2]} gap={4}>
          {slice.items.map((item, index) => (
            <GridItem key={index} item={item} />
          ))}
        </Grid>
      </Container>
    </Section>
  )
}

export default CompanyGrid

I want to access the data of the company type on the GridItem Component. I found out that when I add a graphquery to the Page Component that i have access to the data. But when I do this, I can't access the other slices data anymore:

export default async function Page({ params }: { params: Params }) {
  const client = createClient()
  const page = await client
    .getByUID('page', params.uid, {
      lang: params.lang,
      graphQuery: `
    {
      page {
        slices {
          ...on company_grid {
            variation {
              ...on default {
                items {
                  company {
                    ...on company {
                      name
                      logo
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
    `,
    })
    .catch(() => notFound())

Is this the right way to do this?

Thanks in advance!

Cheers,
Marvin

Hi @marvin.bernd ! Welcome to Prismic's Community. I'm a fellow Prismic user and fan.

I've read through your post and done my best to digest it.

When you say "group field," do you mean the "repeatable zone" in a slice? In particular, it looks like your using the repeatable zone in your Company Grid slice to allow content editors to link to specific Company documents. Is that accurate?

What you describe about seeing the data but then lose other slice's data lines up with my experience. And, believe it or not, that is by design. With graphQuery, you're telling Prismic exactly what you want, if you don't see it in your Query, you're not going to get it. Often times, I rely on "fetchLinks" instead of GraphQuery. I use GraphQuery when I need it or if I'm "feeling fancy."

So my recommendation would be to ensure your graphQuery has all the fields you want in your returned data object, OR... use fetchLinks and see if that works. See my fetchLinks example below:

const page = await client
    .getByUID('page', params.uid, {
      lang: params.lang,
    fetchLinks: ['company.logo', 'company.name'],
    })

See if that works (I think it will). If you need the GraphQuery, you just need to add the bits you're looking for back into the query.

1 Like

Thank you so much @nf_mastroianni! fetchLinks worked for me.

Yes I meant a "repeatable zone" instead of a "group field".

I also managed to solve the TypeScript errors by casting the ContentRelationshipField. And adding a type check by asking if the Field is empty.

/**
 * Props for `CompanyGrid`.
 */
export type CompanyGridProps = SliceComponentProps<Content.CompanyGridSlice>

/**
 * Grid item component.
 */
const GridItem = ({ item }: { item: ContentRelationshipField<'company'> }) => {
  if (!isFilled.contentRelationship(item)) return null

  const { name, logo } = item.data as CompanyDocumentData

  return (
    <Box>
      <PrismicNextImage field={logo} fallbackAlt="" />
      <Heading as="h3">{name}</Heading>
    </Box>
  )
}

/**
 * Component for "CompanyGrid" Slices.
 */
const CompanyGrid = ({ slice }: CompanyGridProps): JSX.Element => {
  return (
    <Section
      data-slice-type={slice.slice_type}
      data-slice-variation={slice.variation}
    >
      <Container>
        <SectionHeadline
          title={slice.primary.title}
          headline={slice.primary.headline}
          hasArrow
        />
        <Grid columns={[1, null, 2]} gap={4}>
          {slice.items.map(({ company }, index) => (
            <GridItem key={index} item={company} />
          ))}
        </Grid>
      </Container>
    </Section>
  )
}