Types for content relationship in a Slice

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:

1 Like