Types for content relationship in a Slice

Hello, I am wondering if we could get an example on how to add types for a content relationship that comes from a slice? The docs have an example for a content relationship field on the custom type itself using fetchlinks, but I am having trouble trying to find out how to add types to a content relationship that comes from a slice while still using the fetchlinks option in getStaticProps.

For example when I loop through slice.items my blog_post has the type of EmptyLinkField<"Document"> | FilledContentRelationshipField<string, string, unknown> as generated by Slice Machine.

So using fetchlinks I am able to display each blog post's title using item.blog_post.data.title in a PrismicRichText component but I get Property 'data' does not exist on type 'EmptyLinkField<"Document"> | FilledContentRelationshipField<string, string, unknown>'. Property 'data' does not exist on type 'EmptyLinkField<"Document">'..

Any leads or insight would be greatly appreciated!

Hi Joseph,

Typing a deeply nested Content Relationship field with a data property is challenging if you do it from the top-level client query, which is shown in the documentation's example (see here).

If you need to type a Slice's Content Relationship field, I recommend doing runtime checking with type predicates. This saves you from performing complicated TypeScript gymnastics to override the Slice's types, which can contain many branches (think: different Slice types, each with their own variations, and each with primary and items fields).

A Content Relationship's data field is typed as unknown by default, which works well with type predicates.

The following example checks that a Content Relationship field contains a data property with a parent field. Remember that the data property is only filled if the top-level query included a fetchLinks or graphQuery option, which may not always be the case.

import { Content } from "@prismicio/client";
import { PrismicText, SliceComponentProps } from "@prismicio/react";
import * as prismicH from "@prismicio/helpers";
import * as prismicT from "@prismicio/types";

const hasParentData = <
  TContentRelationshipField extends prismicT.ContentRelationshipField
>(
  contentRelationshipField: TContentRelationshipField
): contentRelationshipField is TContentRelationshipField & {
  data: {
    parent: Pick<Content.PageDocument["data"], "title">;
  };
} => {
  return (
    prismicH.isFilled.contentRelationship(contentRelationshipField) &&
    typeof contentRelationshipField.data === "object" &&
    contentRelationshipField.data !== null &&
    "parent" in contentRelationshipField.data
  );
};

export default function Image({
  slice,
}: SliceComponentProps<Content.ImageSlice>) {
  return (
    <div>
      {hasParentData(slice.primary.link) && (
        <PrismicText field={slice.primary.link.data.parent.title} />
      )}
    </div>
  );
}

The hasParentData function checks that the provided field contains a data property, is an object, and contains a parent field. It types the data property by adding a data.parent property to the provided field (see the is keyword in the function's return type).

If you check this code in your editor, you'll see that slice.primary.link.data.parent.title is typed as PageDocument['data']['title'] (in my case, that is a Rich Text field). You will need to adjust the type names according to your project.

This pattern can be used across your app whenever you need to check if a Content Relationship field has linked fields.

If you have any questions, let me know! :slight_smile: