Watch a live product demo 👀 See how Sanity powers richer commerce experiences

How to Set the Value of a Read Only String Field in a Document Based on Other Fields?

19 replies
Last updated: Aug 14, 2020
This has most likely been answered before but I'm not having much luck searching. I'd like to set the value of a read only string field based on the value of 3 other fields in the document, 2 of which are references. I currently have a custom component that wraps all 4 fields but I'm having a hard time getting the value I need from the reference fields. Is there a way to do this?
Aug 11, 2020, 6:08 PM
Here is my custom component for reference
import PropTypes from 'prop-types'
import React from 'react'
import client from 'part:@sanity/base/client'
import Fieldset from 'part:@sanity/components/fieldsets/default'
import {setIfMissing} from 'part:@sanity/form-builder/patch-event'
import {FormBuilderInput} from 'part:@sanity/form-builder'

export default class CustomObjectInput extends React.PureComponent {
  static propTypes = {
    type: PropTypes.shape({
      title: PropTypes.string,
      name: PropTypes.string
    }).isRequired,
    level: PropTypes.number,
    value: PropTypes.shape({
      _type: PropTypes.string
    }),
    focusPath: PropTypes.array.isRequired,
    onFocus: PropTypes.func.isRequired,
    onChange: PropTypes.func.isRequired,
    onBlur: PropTypes.func.isRequired
  }

  firstFieldInput = React.createRef()

  handleFieldChange = (field, fieldPatchEvent) => {
    const {onChange, type , value} = this.props

    let region = value.region || ''
    let language = value.language || ''
    let course = value.courseType

    let name = `${region} ${language} ${course}`
    
    // Whenever the field input emits a patch event, we need to make sure to each of the included patches
    // are prefixed with its field name, e.g. going from:
    // {path: [], set: <nextvalue>} to {path: [<fieldName>], set: <nextValue>}
    // and ensure this input's value exists
    onChange(fieldPatchEvent.prefixAll(field.name).prepend(setIfMissing({_type: type.name})))
  }

  focus() {
    this.firstFieldInput.current.focus()
  }

  render() {
    const {type, value, level, focusPath, onFocus, onBlur} = this.props
    return (
      <Fieldset>
        <div >
          {type.fields.map((field, i) => (
            // Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component
            // for the given field type
            <div style={{marginBottom: `16px`}}>
              <FormBuilderInput
                level={level + 1}
                ref={i === 0 ? this.firstFieldInput : null}
                key={field.name}
                type={field.type}
                value={value && value[field.name]}
                onChange={patchEvent => this.handleFieldChange(field, patchEvent)}
                path={[field.name]}
                focusPath={focusPath}
                onFocus={onFocus}
                onBlur={onBlur}
              />
            </div>
          ))}
        </div>
      </Fieldset>
    )
  }
}
Aug 11, 2020, 6:08 PM
I need to be able to get the name field from the region and language references.
Aug 11, 2020, 6:09 PM
Perhaps a different approach here would be to use Document Actions instead of the custom component? https://www.sanity.io/docs/document-actions
You could make a new publish action that fills out the read only fields just before publishing.
Aug 12, 2020, 7:24 PM
To get the data from the referenced documents you can use the client to query for the those documents

import client from 'part:@sanity/base/client'

const doc = await client.getDocument(your-ref-doc-id)
Aug 12, 2020, 7:28 PM
An alternative route would be to skip filling in data from the other documents all togheter. It's kind of an anti-pattern in my mind.
Instead you should be able to include the data from the referenced documents in your front end queries quite easily. Especially if you're using groq in the front end.
Aug 12, 2020, 7:30 PM
Hey, thanks for getting back to me. I'm probably missing out a vital step somewhere but I've created the action and registered it following the docs above. My action, for now, is almost exactly the example you posted.
import { useState, useEffect } from 'react'
import { useDocumentOperation } from '@sanity/react-hooks'

export function UpdateInstanceName(props) {
  const {patch, publish} = useDocumentOperation(props.id, props.type)
  const [isPublishing, setIsPublishing] = useState(false)
  
  useEffect(() => {
    // if the isPublishing state was set to true and the draft has changed
    // to become `null` the document has been published
    console.log(isPublishing && !props.draft)
    if (isPublishing && !props.draft) {
      setIsPublishing(false)
    }
  }, [props.draft])
  
  return {
    disabled: publish.disabled,
    label: isPublishing ? 'Publishing…' : 'Publish',
    onHandle: () => {
      console.log("handle Something")
      // This will update the button text 
      setIsPublishing(true)
      
      // Set publishedAt to current date and time
      patch.execute([{set: {name: "Test Name"}}])
      
      // Perform the publish
      publish.execute()
      
      // Signal that the action is completed
      props.onComplete() 
    }
  }
}
The first
console.log
happens so I know it's hooked up but it seems like the
onHandle
callback is never called as the
console.log
within it never logs. Can you see what I'm doing wrong?
Aug 13, 2020, 8:58 AM
Hi again! That seem to work for me. Did you register the action properly? https://www.sanity.io/docs/document-actions#2-register-and-resolve-document-actions-3254d0188d3e
Aug 13, 2020, 10:08 AM
You probably also want to replace the default publish action for that document type, which you can do like in this example https://www.sanity.io/docs/document-actions#selectively-replacing-builtin-actions-b34d4d68c564
Aug 13, 2020, 10:16 AM
Sorry to keep going on about it but I think I'm not getting my head properly around it. As I said before the first
console.log
within the useEffect logs so that should indicate that it's properly registered, right? It's the
console.log
in the
onHandle
callback that doesn't log when I publish the document, should it? I assumed it should but maybe that's wrong.
Aug 14, 2020, 7:48 AM
How does your
resolveDocumentActions.js
look?
Aug 14, 2020, 8:22 AM
Also, if you're not rewriting the default Publish action, your action will be in the dropdown arrow next to the Publish button. For testing purposes it could be wise to label the action differently:

    label: isPublishing ? 'Testing…' : 'My test action',
Aug 14, 2020, 8:25 AM
Thanks for continuing to help, my
resolveDocumentActions.js
looks like this:
import defaultResolve from 'part:@sanity/base/document-actions'

import { UpdateInstanceName } from '../actions/updateInstanceName'

export default function resolveDocumentActions(props) {
  return [...defaultResolve(props), UpdateInstanceName]
}
Aug 14, 2020, 8:28 AM
And you are sure you are executing the right action from the dropdown menu?
Aug 14, 2020, 8:29 AM
I changed the label as you suggested and it appears in the menu now. So if I click that instead of publish it works as I expect. Thank you.
Aug 14, 2020, 8:30 AM
Or rather it always appeared in the menu but I was getting confused about my Publish action and the default Publish action.
Aug 14, 2020, 8:32 AM
Cool!
If you want to replace the Publish
button you will have to do this:

import defaultResolve, {PublishAction} from 'part:@sanity/base/document-actions'
import {UpdateInstanceName} from './updateInstanceName'

export default function resolveDocumentActions(props) {
  return defaultResolve(props)
    .map(Action =>
      Action === PublishAction ? UpdateInstanceName : Action
    )
}
Aug 14, 2020, 8:32 AM
Amazing, thank you.
Aug 14, 2020, 8:33 AM
You're welcome!
Aug 14, 2020, 8:37 AM

Sanity– build remarkable experiences at scale

The Sanity Composable Content Cloud is the modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Related answers

Get more help in the community Slack

TopicCategoriesFeaturedRepliesLast Updated
After adding the subtitle and running this code npm run graphql-deploy It does nothingSep 15, 2020
how to limit a reference to just one entry in Studio reference input side versus the default as-many-entries-as-you-fill-in-an-array...Sep 18, 2020
Is it possible to fetch more than one "_type" using GROQ?Nov 2, 2020
I want to add a view with the Structure builder (S.view.component) where I list similar documents based on the title. What...Sep 23, 2020
Is there a structure builder example where the format of each preview for the document list is modified?Feb 3, 2021
I have an array of references to a country schema type but it always just returns NULL values for meJan 30, 2021
Hi, I need help with a query for getting the url of an image asset. Here is what I've been trying, but I only get the _ref...Dec 1, 2020
Sanity UI looks brilliant :smiley: Is something like the current date picker possible at the moment? I’m not sure if anicon...Dec 21, 2020
Hey everyone. I have been coding and may have potentially accidentally deleted something. Does anyone know how to resolve...Dec 26, 2020
Hello everyone and happy new year :raised_hands::skin-tone-2:, I have a problem with outputting Portable Text :disappointed:...Jan 1, 2021

Related contributions

Clean Next.js + Sanity app
- Template

Official(made by Sanity team)

A clean example of Next.js with embedded Sanity ready for recomposition.

Cody Olsen
Go to Clean Next.js + Sanity app

Blog with Built-in Content Editing
- Template

Official(made by Sanity team)

A Sanity-powered blog with built-in content editing and instant previews.

Go to Blog with Built-in Content Editing