Dynamic checkbox input component based on another field's value

10 replies
Last updated: Dec 2, 2025
Hi everyone! Is it possible to build a custom input component that dynamically displays a list of checkboxes w/ labels based on the current value of separate field?
For example, say it was a shopping list, and the goal would be for the contents of the list to update based on which meal was selected. I have the document schemas set up for this example (will post in thread below), but am struggling to get the component to 1) update the list when the new meal is selected, and 2) patch the current list to content lake.
AI Update

Yes, absolutely! This is a great use case for a custom input component with useFormValue. Here's how to build it:

Basic Structure

Your custom input component needs to:

  1. Use useFormValue to watch the meal field
  2. React to changes with useEffect
  3. Use onChange with set() and unset() to patch the Content Lake

Here's a complete example for your shopping list scenario:

import { useFormValue, set, unset } from 'sanity'
import { useEffect, useState, useCallback } from 'react'
import { Stack, Checkbox, Card } from '@sanity/ui'

export const ShoppingListInput = (props) => {
  const { onChange, value = [] } = props
  const mealRef = useFormValue(['meal']) // Watch the meal field
  const [ingredients, setIngredients] = useState([])

  // Fetch ingredients when meal changes
  useEffect(() => {
    if (mealRef?._ref) {
      // Query Sanity for the meal's ingredients
      client.fetch(
        `*[_id == $mealId][0].ingredients`,
        { mealId: mealRef._ref }
      ).then(setIngredients)
    } else {
      setIngredients([])
    }
  }, [mealRef?._ref]) // Re-run when meal reference changes

  const handleCheckboxChange = useCallback((ingredient, checked) => {
    let newValue
    if (checked) {
      // Add ingredient to array
      newValue = [...value, ingredient]
    } else {
      // Remove ingredient from array
      newValue = value.filter(item => item._key !== ingredient._key)
    }
    
    // Patch to Content Lake - correct v3 pattern
    onChange(newValue.length > 0 ? set(newValue) : unset())
  }, [onChange, value])

  return (
    <Stack space={3}>
      {ingredients.map((ingredient) => (
        <Card key={ingredient._key} padding={2}>
          <Checkbox
            checked={value.some(v => v._key === ingredient._key)}
            onChange={(e) => handleCheckboxChange(ingredient, e.target.checked)}
          >
            {ingredient.name}
          </Checkbox>
        </Card>
      ))}
    </Stack>
  )
}

Key Points

1. The useFormValue hook watches other fields in your document. Pass it an array path like ['meal'] for a top-level field, or ['parent', 'child'] for nested fields.

2. The dependency array in useEffect should include mealRef?._ref (not just mealRef) to trigger updates when the actual reference changes, not just the object identity.

3. Patching with onChange (Studio v3): As shown in the official guide, you pass the result of set() or unset() directly to onChange():

onChange(nextValue ? set(nextValue) : unset())

Important: In Studio v3, you do NOT use PatchEvent.from() - that was the v2 pattern. The v3 API is simpler and cleaner.

4. Performance optimization: Wrap your change handler in useCallback with onChange and value in the dependency array to prevent unnecessary re-renders.

Schema Setup

In your schema, attach the component like this:

{
  name: 'shoppingList',
  type: 'array',
  of: [{ type: 'reference', to: [{ type: 'ingredient' }] }],
  components: {
    input: ShoppingListInput
  }
}

Common Gotchas

  • Context requirement: useFormValue only works inside custom input components, not in document actions or other contexts
  • Real-time updates: The hook automatically re-renders when the watched field changes
  • Array handling: Make sure each ingredient has a _key for proper React rendering and array patching
  • Import from 'sanity': Both set and unset are imported from the 'sanity' package in v3

This pattern works great for dependent fields, conditional inputs, and dynamic lists based on other selections!

Show original thread
10 replies

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?