Discussion on displaying file URLs for a custom component in a Sanity.io project

8 replies
Last updated: Aug 22, 2021
Hi, I have written a custom component to display a file url below the file upload field. So far, it correctly displays the url, but only for the first element of the array (works if I reorder the elements as well). How could I make sure I get the correct values for each element of the array? (Code and screenshots are attached below)
AI Update

Based on your description, it sounds like you're creating a custom component for array items that displays file URLs, but the component is showing the same value (from the first array element) for all items. This is a common issue when working with array fields in Sanity Studio.

The problem is likely that your component needs to access the correct item-specific value rather than reading from the document root. In Sanity Studio v3, you should use the value prop passed directly to your custom component, or use hooks like useFormValue() with the correct path.

Here's what typically causes this issue and how to fix it:

The Issue: When you use useFormValue([]) or access the document root, you're getting the entire document, and if you're then manually accessing an array by index, you might be inadvertently always accessing the first item.

The Solution: Your custom component receives a value prop that contains the specific array item's data. Use this directly:

import { Stack, Text } from '@sanity/ui'

const FileUrlDisplay = (props) => {
  const { value, renderDefault } = props
  
  // value contains the specific array item's data
  const fileUrl = value?.asset?.url || 'No file uploaded'
  
  return (
    <Stack space={3}>
      {renderDefault(props)}
      <Text size={1} muted>
        File URL: {fileUrl}
      </Text>
    </Stack>
  )
}

export default FileUrlDisplay

If you need to access the specific path for this array item using hooks, you can use the path prop that's passed to your component:

import { useFormValue } from 'sanity'
import { Stack, Text } from '@sanity/ui'

const FileUrlDisplay = (props) => {
  const { renderDefault, path } = props
  
  // This gets the value at the specific path for this array item
  const itemValue = useFormValue(path)
  const fileUrl = itemValue?.asset?.url || 'No file uploaded'
  
  return (
    <Stack space={3}>
      {renderDefault(props)}
      <Text size={1} muted>
        File URL: {fileUrl}
      </Text>
    </Stack>
  )
}

Key Points:

  • Each array item component receives its own value and path props
  • The path prop contains the correct path to that specific array item (e.g., ['files', 0], ['files', 1], etc.)
  • Using props.value directly is usually the simplest approach
  • If you use useFormValue(), make sure to pass the path prop, not a hardcoded path

Without seeing your specific code, I can't pinpoint the exact issue, but the most common mistake is using useFormValue(['arrayFieldName', 0]) with a hardcoded 0 index, which would always grab the first item. Always use the path prop or value prop that's passed to your component to ensure you're accessing the correct array item's data.

FontUrl.js component code:
import React from 'react'
import { withDocument } from 'part:@sanity/form-builder'

// build a file url
export function fileUrl(reference) {
  const project = {
    projectId: process.env.SANITY_STUDIO_API_PROJECT_ID,
    dataset: process.env.SANITY_STUDIO_API_DATASET || 'production'
  }
  
  const referenceArray = reference.split('-')
  const asset = {
    assetId: referenceArray[1],
    extension: referenceArray[2]
  }

  return `<https://cdn.sanity.io/files/${project.projectId}/${project.dataset}/${asset.assetId}.${asset.extension}>`
}

const FontUrl = React.forwardRef((props) => {
  for(let i=0; i<props.document.fontFiles.length; i++) {
    const reference = props.document.fontFiles[i].asset._ref

    return (
      <p>{fileUrl(reference)}</p>
    )
  }
})

export default withDocument(FontUrl)
A snippet of the schema code:
{
      name: 'fontFiles',
      title: 'Font files',
      description: 'Upload font files, add below with @font-face. Accepted formats: .woff, .woff2',
      type: 'array',
      of: [
        {
          type: 'file',
          options: {
            accept: '.woff2, .woff',
          },
          fields: [{
            name: 'fontUrl',
            title: 'Font URL',
            description: 'Use in CSS @font-face',
            type: 'url',
            // below adding a component
            inputComponent: FontUrl,
            readOnly: true,
            options: {
              isHighlighted: true,
            }
          }],
        }
      ]
    },
Screenshots (sorry, components as well as other details are not finished yet)
Hi Paul, do you want
<FontUrl>
to display the urls of all uploaded fonts? If so, you should change the code so FontUrl doesn’t return from within a loop (will only loop once).
You could do something like this:


const FontUrl = React.forwardRef((props) => {
  const { fontFiles } = props.document
  return (
    <>
      <p>{fontFiles.map(fontFile => fileUrl(fontFile.asset._ref)})}</p>
    </>
  )
})
It's slightly different. I wanted to display only the url for the selected/expanded field. You can see on the screenshots that currently it displays the the url of the first font (.woff2) in both cases. I wonder if it would be possible to display .woff2 for the first one and .woff for the second one (with other data being different as well, of course)
In that case you’ll need to import
withValuePath
as well, as well, it will show you which array the current custom field belongs to, so you can get the file.

import { withDocument, withValuePath } from 'part:@sanity/form-builder'

export const FontUrl = withValuePath(withDocument(({ document, getValuePath }) => {
  console.log(document) // { _id: ... }
  console.log(getValuePath()) // ["content", { _key: ... }]
}))

Finally finished it! I think it turned out quite nice.
const FontUrl = React.forwardRef((props, ref) => {
  const matchingUrl = props.document.fontFiles.find(fontFile => fontFile._key === props.getValuePath()[1]._key);

  return (
    <p>{matchingUrl.asset ? fileUrl(matchingUrl.asset._ref) : null}</p>
  )
})

export default withValuePath(withDocument(FontUrl))
Thank you so much for helping me out!
Congrats Paul! If you'd like to style the url to look the same as other fields in the studio, check out sanity ui! https://www.sanity.io/ui/docs

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?