ricokahler / sanity-codegen

Generate TypeScript types from your Sanity.io schemas
sanity-codegen-dev.vercel.app
MIT License
270 stars 19 forks source link

Flatten types #211

Closed tobbbe closed 3 years ago

tobbbe commented 3 years ago

Hello! thanks for a great package!

Would it be possible to flatten all types? It would be great because right now I cant, for example, pass a nested property to a helper render function:

ex:

function Page() {
  return (
    <div>{someSanitySchema.content.map(x => {
      if (x._type === 'textSection') return <TextSection data={x.textSectionContent} />;
      if (x._type === 'gallerySection') return <GallerySection data={x} />;
    })}</div>
  )
}
function TextSection({data}: {data: CANT_USE_THIS_PROP}) { // <----
  return (
    <div>{data.OHNO}</div>
  )
}
function GallerySection({data}: {data: CANT_TYPE_THIS_PROP}) { // <----
  return (
    <div>{data.OHNO}</div>
  )
}

Right now this is what we get:

export interface Case extends SanityDocument {
  _type: 'case';
  title?: string;
  slug?: { _type: 'slug'; current: string };
  content?: Array<
    | SanityKeyed<{
        _type: 'textSection';
        textSectionContent?: Array<SanityKeyed<SanityBlock>>;
      }>
    | SanityKeyed<{
        _type: 'gallerySection';
        display?: 'centered' | 'full-width';
        images?: Array<
          SanityKeyed<{
            _type: 'image';
            asset: SanityReference<SanityImageAsset>;
            crop?: SanityImageCrop;
            hotspot?: SanityImageHotspot;
            alt?: string;
          }>
        >;
      }>
  >;
}

export type Documents = Case;

But this would help

Because then we can import the nested types (CaseTextSection and CaseContentGallery) and use them in function TextSection/GallerySection πŸ˜ƒ

  export interface Case extends SanityDocument {
    _type: 'case';
    title?: string;
    slug?: { _type: 'slug'; current: string };
    content?: Array<
      | CaseContentText
      | CaseContentGallery
    >;
  }

  export type CaseContentText = SanityKeyed<{
    _type: 'textSection';
    textSectionContent?: Array<SanityKeyed<SanityBlock>>;
  }>

  export type CaseContentGallery = SanityKeyed<{
    _type: 'gallerySection';
    display?: 'centered' | 'full-width';
    images?: Array<
      SanityKeyed<{
        _type: 'image';
        asset: SanityReference<SanityImageAsset>;
        crop?: SanityImageCrop;
        hotspot?: SanityImageHotspot;
        alt?: string;
      }>
    >;
  }>

  export type Documents = Case;
ricokahler commented 3 years ago

Hi thank you for the kind words.

For this issue, I think you have two options:

  1. In the sanity schema's themselves, register/create your own type for the cases you need. See the example here. When you create a registered type in the sanity schema, the codegen will generate named types similar to document types for you to import.
  2. Reach into the type using the index-access syntax and typescript helpers e.g.
import {Case} from '../your-types'
type CaseContentText = Extract<NonNullable<Case['content']>[number], {_type: 'textSection'}>

playground link

There's a lot of TypeScript going on there so let me break it down:

import {Case} from '../your-types'
type Content = Case['content']                               // grab the property using index-access
type NonNullableContext = NonNullable<Content>               // remove undefined from the type
type Item = NonNullableContext[number]                       // unwrap the type from the array using index-access again
type CaseContentText = Extract<Item, {_type: 'textSection'}> // https://stackoverflow.com/a/52943170/5776910

This answer https://github.com/ricokahler/sanity-codegen/discussions/196#discussioncomment-1359118 is relevant too

tobbbe commented 3 years ago

Wow, wow, WOW! Thank you so much! That solves my question and taught me some very interesting typescript and sanity stuff πŸ˜ƒ Have a great day!