Trouble defining TypeScript props on a preview component in Sanity

6 replies
Last updated: Mar 31, 2023
There is not much info on the website on howTo define typescript props on a preview component. Tried creating mine but it’s not working I keep getting errors.


import { defineField, defineType } from 'sanity'
import getYouTubeId from 'get-youtube-id'
import LiteYouTubeEmbed from 'react-lite-youtube-embed'

import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'

type PreviewProps = {
  url: string
  title: string
  renderDefault: any
}

const Preview = ({
  url,
  title = 'YouTube Embed',
  renderDefault
}: PreviewProps) => {
  // const { url, title = 'YouTube Embed', renderDefault } = props

  if (!url) {
    return <div>Missing YouTube URL</div>
  }
  const id = getYouTubeId(url)

  return (
    <div>
      {renderDefault({ title })}
      <LiteYouTubeEmbed title={title} id={id as string} />
    </div>
  )
}

export default defineType({
  name: 'youtube',
  type: 'object',
  title: 'YouTube Embed',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      title: 'YouTube video title'
    }),
    defineField({
      name: 'url',
      type: 'url',
      title: 'YouTube video URL'
    })
  ],
  preview: {
    select: {
      title: 'title',
      url: 'url'
    }
  },
  components: {
    preview: Preview
  }
})
AI Update

The issue with your code is that you need to properly type your preview component props. While PreviewProps can be imported from 'sanity', it doesn't automatically know about the custom fields you're selecting in your preview.select configuration. You need to extend it with a type cast.

Here's your corrected code:

import { defineField, defineType } from 'sanity'
import type { PreviewProps } from 'sanity'
import getYouTubeId from 'get-youtube-id'
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'

// Extend PreviewProps with your selected fields
type YouTubePreviewProps = PreviewProps & {
  url?: string
  title?: string
}

const Preview = (props: PreviewProps) => {
  // Cast to access your custom fields
  const { url, title = 'YouTube Embed' } = props as YouTubePreviewProps

  if (!url) {
    return <div>Missing YouTube URL</div>
  }
  
  const id = getYouTubeId(url)

  return (
    <div>
      {props.renderDefault({ ...props, title })}
      <LiteYouTubeEmbed title={title} id={id as string} />
    </div>
  )
}

export default defineType({
  name: 'youtube',
  type: 'object',
  title: 'YouTube Embed',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      title: 'YouTube video title'
    }),
    defineField({
      name: 'url',
      type: 'url',
      title: 'YouTube video URL'
    })
  ],
  preview: {
    select: {
      title: 'title',
      url: 'url'
    }
  },
  components: {
    preview: Preview
  }
})

Key changes and why they matter:

  1. Import PreviewProps from 'sanity' - This gives you the base type that includes essential props like renderDefault

  2. Create an extended type - Use a type intersection to add your selected fields:

    type YouTubePreviewProps = PreviewProps & {
      url?: string
      title?: string
    }
  3. Cast the props - Since TypeScript can't automatically infer what fields come from preview.select, you need to cast: props as YouTubePreviewProps

Why preview components work differently: According to the official guide on creating richer array item previews, preview components don't receive the field's actual value or path like other form components. Instead, they only get the values you explicitly define in preview.select. This is why you need to manually type-cast to access those fields.

The documentation shows this exact pattern:

type CastPreviewProps = PreviewProps & {
  discount?: number
  validUntil?: string
}

const castProps = props as CastPreviewProps
const {discount, validUntil} = castProps

This approach gives you full TypeScript support while working within Sanity's preview component architecture!

Hi
user Q
. I've found exploring where Sanity declares and uses types in their node_modules package or in their GitHub repo helps with this. Perhaps in this instance you could try using and expanding on:

import { PreviewProps } from 'sanity'
Tried those but applying it to the props but it still complains about the url etx
Have you tried something like:

PreviewProps & { url: string }
Let me try thank you Oliver i wil let you know
user H


import { PreviewProps, defineField, defineType } from 'sanity'
import getYouTubeId from 'get-youtube-id'
import LiteYouTubeEmbed from 'react-lite-youtube-embed'

import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'

const Preview = (props: PreviewProps & { url: string }) => {
  const { url, title = 'YouTube Embed', renderDefault } = props

  if (!url) {
    return <div>Missing YouTube URL</div>
  }
  const id = getYouTubeId(url)

  return (
    <div>
      {renderDefault({ ...props, title })}
      <LiteYouTubeEmbed title={title as string} id={id as string} />
    </div>
  )
}

export default defineType({
  name: 'youtube',
  type: 'object',
  title: 'YouTube Embed',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      title: 'YouTube video title'
    }),
    defineField({
      name: 'url',
      type: 'url',
      title: 'YouTube video URL'
    })
  ],
  preview: {
    select: {
      title: 'title',
      url: 'url'
    }
  },
  components: {
    preview: Preview
  }
})
Getting the following error now:


Type '(props: PreviewProps & {    url: string;}) => JSX.Element' is not assignable to type 'ComponentType<PreviewProps<PreviewLayoutKey>> | undefined'.
  Type '(props: PreviewProps & {    url: string;}) => JSX.Element' is not assignable to type 'FunctionComponent<PreviewProps<PreviewLayoutKey>>'.
    Types of parameters 'props' and 'props' are incompatible.
      Type 'PreviewProps<PreviewLayoutKey>' is not assignable to type 'PreviewProps<PreviewLayoutKey> & { url: string; }'.
        Property 'url' is missing in type 'PreviewProps<PreviewLayoutKey>' but required in type '{ url: string; }'.
Hey
user Q
. Not sure why you are getting an error in this case. You could maybe use url as optional:

{ url?: string; }
but perhaps someone with better TS knowledge than myself may be able to help
😁

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?