Custom preview component not working for object field in Sanity array

13 replies
Last updated: May 14, 2024
i'm following along on https://www.sanity.io/docs/form-components#0319196f086b and i can't seem to get the preview form component to work, but the input component works just fine

defineField({
      name: 'productVideos',
      type: 'array',
      of: [
        defineArrayMember(
          defineType({
            name: 'video',
            type: 'object',
            fields: [
              defineField({
                type: 'string',
                name: 'url',
                validation: (rule) => rule.required(),
                components: {
                  input: VideoInput,
                  preview: VideoPreview,
                },
              }),
            ],
          }),
        ),
      ],
    }),
here are the referenced components


import {PreviewProps} from 'sanity'

export function VideoPreview(props: PreviewProps) {
  return <div style={{border: '1px solid green'}}>{props.renderDefault(props)}</div>
}

import {StringInputProps} from 'sanity'
import {Box, Card, Flex, TextInput} from '@sanity/ui'
import ReactPlayer from 'react-player'

export function VideoInput({elementProps, value}: StringInputProps) {
  return (
    <Card marginBottom={4}>
      <Flex direction="column">
        <Box flex={1}>
          <TextInput {...elementProps} />
        </Box>
        {value && (
          <Box display="flex" marginTop={4}>
            <ReactPlayer url={value} />
          </Box>
        )}
      </Flex>
    </Card>
  )
}
AI Update

I can see the issue! According to the Form Components documentation, the preview component is specifically designed to customize "how an object, image, or reference value is displayed in list views" - particularly for arrays of objects.

The problem with your code is that you've placed the preview component on the url string field itself. Preview components on primitive types like strings don't render in the same way as object-level previews. They're meant to customize how objects appear in array list views, not individual primitive fields.

Your VideoInput component works perfectly because input components replace the actual form input widget for that field. However, the preview component needs to be at the object type level (the video object), not on the individual url string field inside it.

Here's the corrected schema:

defineField({
  name: 'productVideos',
  type: 'array',
  of: [
    defineArrayMember(
      defineType({
        name: 'video',
        type: 'object',
        components: {
          preview: VideoPreview, // Move preview here to the object level
        },
        fields: [
          defineField({
            type: 'string',
            name: 'url',
            validation: (rule) => rule.required(),
            components: {
              input: VideoInput, // Input stays on the field
            },
          }),
        ],
      }),
    ),
  ],
}),

Now your VideoPreview component will render when viewing the array list items. You'll also want to add a preview configuration block to tell Sanity what data to pass to your preview component:

defineType({
  name: 'video',
  type: 'object',
  components: {
    preview: VideoPreview,
  },
  preview: {
    select: {
      url: 'url',
    },
    prepare({url}) {
      return {
        title: url || 'No URL provided',
      };
    },
  },
  fields: [
    defineField({
      type: 'string',
      name: 'url',
      validation: (rule) => rule.required(),
      components: {
        input: VideoInput,
      },
    }),
  ],
})

Your VideoPreview component looks good as-is:

import {PreviewProps} from 'sanity'

export function VideoPreview(props: PreviewProps) {
  return <div style={{border: '1px solid green'}}>{props.renderDefault(props)}</div>
}

The key distinction is: input components control how you edit a field in the form, while preview components control how objects are displayed in list views (like your array of video objects). Preview components work at the object/document level to customize the list item appearance, not on individual primitive fields within those objects.

Show original thread
13 replies
i would expect a green border around the preview component
but it shows the default one
and here is input component working
Hmm, the code looks right at first glance. What version of the Studio are you running?
  "dependencies": {
    "@prismicio/helpers": "^2.3.9",
    "@sanity/code-input": "^4.1.4",
    "@sanity/schema": "^3.41.2",
    "@sanity/ui": "^2.1.7",
    "@sanity/vision": "^3.41.2",
    "@tanstack/react-query": "^5.36.0",
    "@tanstack/react-query-devtools": "^5.36.0",
    "graphql-request": "^7.0.1",
    "groq": "^3.41.2",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-is": "^18.3.1",
    "react-player": "^2.16.0",
    "sanity": "^3.41.2",
    "sanity-plugin-documents-pane": "^2.3.0",
    "sanity-plugin-media": "^2.2.5",
    "styled-components": "^6.1.11",
    "zod": "^3.23.8"
  },
i just updated it too as i thought it might be a bug but even on newer update still same issue
Oh, I think I see the issue now. It looks like you’re specifying the component on the string field, not on the parent video field.
oh man lol
ok i think you're right, let me check
defineField({
      name: 'productVideos',
      type: 'array',
      of: [
        defineArrayMember(
          defineType({
            name: 'video',
            type: 'object',
            fields: [
              defineField({
                type: 'string',
                name: 'url',
                validation: (rule) => rule.required(),
                components: {
                  input: VideoInput,
                },
              }),
            ],
            components: {
              preview: VideoPreview,
            },
          }),
        ),
      ],
    }),
so that did what i wanted, i guess my question, should i do it this way? or should i move the custom input to the object? or just personal preference? i know this changes the input from
StringInputProps
to
ObjectInputProps
I personally would put both components on the
video
object, but that’s just personal preference.
great, thanks
user M
You’re welcome!

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?