Populate string list based on another field value in same document

2 replies
Last updated: May 24, 2023
Hey everyone,
I'm wondering if there is a way to populate a list of predefined strings based on the value of another field in the same document?

Basically what I'm hoping the achieve is this. I have a
collection
document that references
products
and the
collection
can conditionally have sub-categories which are just added as string tags. I was hoping to be able to add a
product
and be able to give it a sub-category based on the ones already defined in the
collection
. I've been able to get it so the editor can just type in a category and it will be invalid unless it matches one already defined in the sub-categories field but this seems a bit hacky. Any advice on how best to achieve this? Perhaps custom input component?
AI Update

Yes, you can definitely populate a list of predefined strings based on another field's value! This is a common pattern and there are a couple of good approaches depending on your needs.

The most flexible solution for your use case is creating a custom input component. This gives you full access to the Sanity client and the useFormValue hook to read values from other fields in the document:

import { useFormValue, useClient, StringInputProps, set } from 'sanity'
import { useEffect, useState } from 'react'

function SubCategoryInput(props: StringInputProps) {
  const client = useClient({ apiVersion: '2023-01-01' })
  const [options, setOptions] = useState<string[]>([])
  
  // Get the collection reference from the current document
  const collectionRef = useFormValue(['collection', '_ref'])
  
  useEffect(() => {
    if (!collectionRef) return
    
    // Fetch the collection document and extract sub-categories
    client.fetch(
      `*[_id == $ref][0].subCategories`,
      { ref: collectionRef }
    ).then((subCategories) => {
      setOptions(subCategories || [])
    })
  }, [collectionRef, client])
  
  return (
    <select
      value={props.value || ''}
      onChange={(e) => props.onChange(set(e.target.value))}
    >
      <option value="">Select a sub-category</option>
      {options.map(category => (
        <option key={category} value={category}>{category}</option>
      ))}
    </select>
  )
}

// In your product schema:
{
  name: 'subCategory',
  type: 'string',
  title: 'Sub Category',
  components: {
    input: SubCategoryInput
  }
}

You can also check out this community pattern for building async list options.

Alternative: Using References

Consider whether your sub-categories should actually be separate documents that you reference, rather than strings. This approach gives you:

  • Better data normalization
  • Easier querying and reuse
  • Built-in reference selection UI with filtering
// Product schema with filtered references
{
  name: 'subCategory',
  type: 'reference',
  to: [{ type: 'subCategory' }],
  options: {
    filter: ({ document }) => {
      const collectionRef = document.collection?._ref
      return {
        filter: '_id in *[_id == $collectionRef][0].subCategories[]._ref',
        params: { collectionRef }
      }
    }
  }
}

Why Not options.list as a Function?

While it would be nice to use options.list as an async function (as discussed in this GitHub issue), it's currently not supported because:

  • options.list functions are synchronous and can't use async operations
  • You can't directly access the Sanity client within the schema definition

The custom input component approach is the current best practice for dynamic lists that depend on other field values or require data fetching. It gives you full control and access to all the hooks you need.

Show original thread
2 replies
Nevermind. Figured out a solution myself using a custom input component as suspected 🙂
For anyone that’s also looking for how to do this, here is an example.

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?