Dynamic checkbox input component based on another field's value
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.
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:
- Use
useFormValueto watch the meal field - React to changes with
useEffect - Use
onChangewithset()andunset()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:
useFormValueonly 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
_keyfor proper React rendering and array patching - Import from 'sanity': Both
setandunsetare 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 thread10 replies
Was this answer helpful?
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.