Conditional field values based on another document type in Sanity
Yes, this makes perfect sense and is absolutely possible! What you're describing is a dependent field pattern where the available options in one field are determined by the value of another field, and both are constrained by a separate definition document. This is a great use case for custom input components.
Here's how to implement it:
Schema Setup
First, define your schemas:
// positionDefinition.ts
export default {
name: 'positionDefinition',
title: 'Position Definition',
type: 'document',
fields: [
{
name: 'issueName',
title: 'Issue Name',
type: 'string',
validation: Rule => Rule.required()
},
{
name: 'stances',
title: 'Stances',
type: 'array',
of: [{type: 'string'}],
validation: Rule => Rule.required().min(1)
}
]
}
// position.ts
import { IssueInput } from './components/IssueInput'
import { StanceInput } from './components/StanceInput'
export default {
name: 'position',
title: 'Position',
type: 'document',
fields: [
{
name: 'issue',
title: 'Issue',
type: 'string',
components: {
input: IssueInput
}
},
{
name: 'stance',
title: 'Stance',
type: 'string',
components: {
input: StanceInput
}
}
]
}Custom Input Components
Now create the custom components using useFormValue and useClient:
// components/IssueInput.tsx
import { StringInputProps, useClient } from 'sanity'
import { useEffect, useState } from 'react'
import { Select } from '@sanity/ui'
export function IssueInput(props: StringInputProps) {
const { value, onChange } = props
const client = useClient({ apiVersion: '2024-01-01' })
const [issues, setIssues] = useState<Array<{ _id: string; issueName: string }>>([])
useEffect(() => {
client
.fetch('*[_type == "positionDefinition"]{ _id, issueName }')
.then(setIssues)
}, [client])
return (
<Select
value={value || ''}
onChange={(event) => onChange(event.currentTarget.value)}
>
<option value="">Select an issue...</option>
{issues.map((issue) => (
<option key={issue._id} value={issue.issueName}>
{issue.issueName}
</option>
))}
</Select>
)
}
// components/StanceInput.tsx
import { StringInputProps, useClient, useFormValue } from 'sanity'
import { useEffect, useState } from 'react'
import { Select, Stack, Text } from '@sanity/ui'
export function StanceInput(props: StringInputProps) {
const { value, onChange } = props
const client = useClient({ apiVersion: '2024-01-01' })
// Access the current document's issue field using useFormValue
const selectedIssue = useFormValue(['issue']) as string | undefined
const [stances, setStances] = useState<string[]>([])
useEffect(() => {
if (!selectedIssue) {
setStances([])
return
}
client
.fetch(
'*[_type == "positionDefinition" && issueName == $issue][0].stances',
{ issue: selectedIssue }
)
.then((result) => setStances(result || []))
}, [client, selectedIssue])
if (!selectedIssue) {
return (
<Stack space={3}>
<Text size={1} muted>
Please select an issue first
</Text>
</Stack>
)
}
return (
<Select
value={value || ''}
onChange={(event) => onChange(event.currentTarget.value)}
>
<option value="">Select a stance...</option>
{stances.map((stance) => (
<option key={stance} value={stance}>
{stance}
</option>
))}
</Select>
)
}How It Works
- IssueInput: Fetches all
positionDefinitiondocuments usinguseClientand displays theirissueNamevalues as dropdown options - StanceInput: Uses
useFormValue(['issue'])to access the currently selected issue value from the document, then fetches the corresponding stances from the matchingpositionDefinition - The stance field automatically updates when the issue changes, showing only the relevant stances for that issue
The key here is the useFormValue hook, which replaced the older withDocument HOC in Studio v3. It allows you to access any field value in the current document, enabling the dependent field behavior you need.
Alternative: Using References for Better Data Integrity
Instead of storing the issue as a string, you could use a reference field for even better data integrity:
{
name: 'issueRef',
title: 'Issue',
type: 'reference',
to: [{type: 'positionDefinition'}]
}Then in your StanceInput, you'd dereference it:
const issueRef = useFormValue(['issueRef']) as { _ref: string } | undefined
useEffect(() => {
if (!issueRef?._ref) {
setStances([])
return
}
client
.fetch('*[_id == $id][0].stances', { id: issueRef._ref })
.then((result) => setStances(result || []))
}, [client, issueRef])This approach ensures that if you rename an issue in positionDefinition, all existing position documents automatically reflect the change, and you maintain referential integrity.
Important Notes
- Make sure to include the dependency array in useEffect to avoid excessive API requests
- The
useClienthook must be called within a React function component, not at the top level - If you want to make the stance field conditionally required, check out the conditional fields documentation for validation patterns
Show original thread8 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.