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
C
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
C
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
C
I need to be able to get the name field from the region and language references.
Aug 11, 2020, 6:09 PM
J
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.
You could make a new publish action that fills out the read only fields just before publishing.
Aug 12, 2020, 7:24 PM
J
Aug 12, 2020, 7:25 PM
J
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
J
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.
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
C
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.
The first
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() } } }
console.loghappens so I know it's hooked up but it seems like the
onHandlecallback is never called as the
console.logwithin it never logs. Can you see what I'm doing wrong?
Aug 13, 2020, 8:58 AM
J
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
J
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
C
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.logwithin the useEffect logs so that should indicate that it's properly registered, right? It's the
console.login the
onHandlecallback 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
J
How does your
resolveDocumentActions.jslook?
Aug 14, 2020, 8:22 AM
J
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
C
Thanks for continuing to help, my
resolveDocumentActions.jslooks 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
J
And you are sure you are executing the right action from the dropdown menu?
Aug 14, 2020, 8:29 AM
C
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
C
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
J
Cool!
If you want to replace the Publish
button you will have to do this:
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
C
Amazing, thank you.
Aug 14, 2020, 8:33 AM
J
You're welcome!
Aug 14, 2020, 8:37 AM
Sanity– build remarkable experiences at scale
Sanity is a 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.