Search code examples
javascriptreactjstypescriptgatsbyprismic.io

Typescript types for Prismic Slices


I'm building a site using Prismic, Gatsby, and Typescript. Part of the appeal of Prismic is the slice feature, which allows you to create dynamic content sections that are easier for content editors to use. I've got a component called SliceZone that maps through all the slices on a page:

SliceZone.tsx

import React from 'react';

import { ImageCta } from 'components/slices/call-to-action/image/ImageCta';
import { Columns } from 'components/slices/columns/Columns';
import { ContentBlock } from 'components/slices/content-block/ContentBlock';
import { Embed } from 'components/slices/embed/Embed';
import { TextHero } from 'components/slices/hero/text/TextHero';
import { Slider } from 'components/slices/slider/Slider';
import { Video } from 'components/slices/video/Video';

interface PageSlicesProps {
  slices: any;
}

const PageSlices = ({ slices }: PageSlicesProps) => {
  const sliceComponents = {
    hero: TextHero,
    slider: Slider,
    content_block: ContentBlock,
    video: Video,
    columns: Columns,
    embed: Embed,
    image_call_to_action: ImageCta,
  };

  return slices.map((slice: any, index: number) => {
    const SliceComponent = sliceComponents[slice.type];
    
    if (SliceComponent) {
      return <SliceComponent slice={slice} key={`slice-${slice.type}-${index}`} />;
    }
  }); 
};

interface SliceZoneProps {
  bodyContent: any;
}

export const SliceZone = ({ bodyContent }: SliceZoneProps) => <PageSlices slices={bodyContent.body} />;

and I'm needing to appropriately type everything. However, I'm getting this error on SliceComponent, specifically sliceComponents[slice.type]:

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ hero: ({ slice }: any) => Element; slider: ({ slice }: any) => Element; content_block: ({ slice }: any) => Element; video: ({ slice }: any) => Element; columns: ({ slice }: any) => Element; embed: ({ slice }: { ...; }) => Element; image_call_to_action: ({ slice }: any) => Element; }'.

How do I create an interface that will appropriately type this? I'm sorry if my question is somewhat muddy, I'm new to React and to Typescript, so the language is a bit gone on me as of yet.


Solution

  • The issue is when your mapping of const sliceComponents =. It's missing a signature, so the return type is implicitly any. As the error says.

    You could make it explicit by saying you have a list of React class components/function components?

    const sliceComponentsMap: {
      [key: string]: typeof Component | FunctionComponent;
    } 
    

    The | is a union operator. This says it can be either of these types.


    But I think would be better to create a common slice class that extends the React Component class.

    export interface sliceCommonProps {
      id: string;
    }
    
    // Interface for base slice props
    export interface sliceProps<P> {
      sliceType: string;
      sliceData: P & sliceCommonProps; // This "&" joins the 'Generic' param P with the common props interface
    }
    
    export interface sliceState {}
    
    // Base class with two generic params (P, S) for passing specific component props and state interfaces later
    export class SliceComponent<
      P = { [key: string]: any },
      S = { [key: string]: any }
    > extends Component<sliceProps<P>, sliceState> {}
    

    And have your slice components extend that.

    export interface ImageCtaProps {
      imageUrl: string;
    }
    
    export class ImageCta extends SliceComponent<ImageCtaProps> {
      render() {
        const { id, imageUrl } = this.props.sliceData;
        return <div>{imageUrl}</div>;
      }
    }
    

    Then when you map them to their components you can say they are of type SliceComponent. And compiler now knows the component should have the correct props.

    const sliceComponents: {[key: string]: typeof SliceComponent;} = {
       hero: TextHero,
       ...
    };
    
    slices.map((slice, index) => {
      const SliceComponent = sliceComponents[slice.type];
      return <SliceComponent sliceData={slice} sliceType={slice.type} />
      );
    });
    
    

    Check this stack