Help needed with creating a custom string input component in Sanity Studio

8 replies
Last updated: Apr 2, 2024
Hey all! I would like to create a custom string input component that updates another string field whenever it is changed. I have given it a go and feel like I am missing something really simple! Any help would be really appreciated!
AI Update

I understand the confusion here! This is a common challenge when starting with custom input components. The key thing to know is that a custom input component's onChange can only update its own field value - you can't directly patch sibling fields from within a component.

However, there's a pattern that works really well for this: instead of trying to update Field B from Field A's component, you make Field B the custom component, and have it read Field A's value and update itself accordingly.

The Right Approach

Here's how to do it using the useFormValue hook:

import { useFormValue } from 'sanity'
import { useEffect } from 'react'
import { set, unset } from 'sanity'

export function DependentStringInput(props) {
  const { onChange, value } = props
  
  // Read the value from the sibling field you want to watch
  const sourceFieldValue = useFormValue(['sourceFieldName'])
  
  // Update this field when the source field changes
  useEffect(() => {
    if (sourceFieldValue) {
      // Derive your value from the source field
      const derivedValue = `Derived from: ${sourceFieldValue}`
      onChange(set(derivedValue))
    } else {
      onChange(unset())
    }
  }, [sourceFieldValue, onChange])
  
  return (
    <input 
      type="text" 
      value={value || ''} 
      readOnly // Make it readonly if it's purely derived
    />
  )
}

Then in your schema:

{
  name: 'myDocument',
  type: 'document',
  fields: [
    {
      name: 'sourceFieldName',
      type: 'string',
      // This is the field you type in - no custom component needed
    },
    {
      name: 'dependentFieldName',
      type: 'string',
      // This field watches the other one and updates itself
      components: { 
        input: DependentStringInput 
      }
    }
  ]
}

Why This Works

The useFormValue hook lets you read any field value from the current document. When the source field changes, your custom component's useEffect runs and patches its own value using its own onChange prop. This is the correct way to create field dependencies in Sanity Studio.

Important Notes

  • useFormValue(['fieldName']) reads sibling fields at the document root level
  • For nested fields, use the full path: useFormValue(['parentObject', 'nestedField'])
  • The onChange prop you receive in a custom component can only update that component's own field - this is by design for data consistency
  • If you need the dependent field to also be manually editable, remove the readOnly prop and add logic to handle user input

This pattern is much more reliable than trying to patch sibling fields directly, and it's the approach recommended in the Sanity community discussions.

👋 What does your component look like so far and what’s not working about it?
Very similar to the one in the documentation here .

import {useCallback} from 'react'
import {Stack, Text, TextInput} from '@sanity/ui'
import {set, unset} from 'sanity'

export const SlugPrefix = (props) => {
  const {elementProps, onChange, value = ''} = props

  const handleChange = useCallback((event) => {
    const nextValue = event.currentTarget.value

    // set another field in the document every time this value changes

    onChange(nextValue ? set(nextValue) : unset())
	}, [onChange])

  return (
      <TextInput
        {...elementProps}
        onChange={handleChange}
        value={value}
      />
  )
}
Thanks for the quick response. It is very similar to the one in the documentation here .

import {useCallback} from 'react'
import {Stack, Text, TextInput} from '@sanity/ui'
import {set, unset} from 'sanity'

export const SlugPrefix = (props) => {
  const {elementProps, onChange, value = ''} = props

  const handleChange = useCallback((event) => {
    const nextValue = event.currentTarget.value

    // SET ANOTHER FIELDS VALUE IN THE DOCUMENT BASED ON THIS VALUE

    onChange(nextValue ? set(nextValue) : unset())
	}, [onChange])

  return (
      <TextInput
        {...elementProps}
        onChange={handleChange}
        value={value}
      />
  )
}
See the comment in the handleChange function. Tried a few things but I must have missed something!
😞
Ah ok I gotchu:
import {useCallback} from 'react'
import {set, unset, useClient, useFormValue} from 'sanity'

export const SlugPrefix = (props) => {
  const {renderDefault, elementProps, onChange, value = ''} = props
  const client = useClient({apiVersion: '2024-03-13'})
  const id = useFormValue(['_id'])

  const handleChange = useCallback(
    (event) => {
      const nextValue = event.currentTarget.value
      //change <otherField> to the field you'd like to patch
      client.patch(id).set({otherField: nextValue}).commit()

      onChange(nextValue ? set(nextValue) : unset())
    },
    [onChange],
  )

  return renderDefault({...props, elementProps: {...elementProps, onChange: handleChange}})
}
Thanks so much! I tried this approach and it worked locally but I had some issues when I deployed it! I'll give it another go 👍
Might not be the recommended way but I removed the useCallback and it seemed to work!
Interesting! I wonder if the dependencies for it were wrong?
ahhh yeah maybe!

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?