Updating a field value of another document when clicking over a boolean type in Sanity.io.

12 replies
Last updated: Nov 2, 2021
Hello Everyone! I have a question. How can i update a field value of another document when clic over a boolean type? I have the following schema:
export default {
    name:"myschema",
    type:"document",
    title:"My Schema",
    description:"MySchema",
    fields:[
        {
            name:"active",
            title:"Active",
            type:"boolean"
        },
        {
            name:"name",
            title:"Event Name",
            type:"string"
        }
    ] 
}
The idea is that only one document can have the active field as true without the editor having to manually unchecked the previous selected event.
AI Update

Hey there! Great question about implementing a "radio button" style behavior for boolean fields across documents. You want only one document to have active: true at a time, with the others automatically set to false when a new one is selected.

The best approach is to create a custom input component for your boolean field that automatically updates other documents when toggled. Here's how to implement it:

Custom Input Component Approach

Create a custom input component that uses the Sanity client to update other documents when the boolean is toggled:

// components/ExclusiveBooleanInput.tsx
import {BooleanInputProps, set, unset} from 'sanity'
import {Switch, Stack, Text} from '@sanity/ui'
import {useCallback} from 'react'
import {useClient} from 'sanity'

export function ExclusiveBooleanInput(props: BooleanInputProps) {
  const {value, onChange} = props
  const client = useClient({apiVersion: '2024-01-01'})

  const handleChange = useCallback(
    async (event: React.ChangeEvent<HTMLInputElement>) => {
      const newValue = event.target.checked

      // If setting to true, deactivate all other documents
      if (newValue) {
        try {
          // Find all other documents where active is true
          const activeDocuments = await client.fetch(
            `*[_type == "myschema" && active == true && _id != $currentId]._id`,
            {currentId: props.id}
          )

          // Create a transaction to update all active documents
          if (activeDocuments.length > 0) {
            const transaction = client.transaction()
            activeDocuments.forEach((docId: string) => {
              transaction.patch(docId, {set: {active: false}})
            })
            await transaction.commit()
          }
        } catch (error) {
          console.error('Error deactivating other documents:', error)
        }
      }

      // Update the current document's value
      onChange(newValue ? set(newValue) : unset())
    },
    [client, onChange, props.id]
  )

  return (
    <Stack space={3}>
      <Switch
        checked={value || false}
        onChange={handleChange}
      />
      {value && (
        <Text size={1} muted>
          This is the active event. Enabling another will deactivate this one.
        </Text>
      )}
    </Stack>
  )
}

Then register it in your schema:

// schemas/myschema.ts
import {ExclusiveBooleanInput} from '../components/ExclusiveBooleanInput'

export default {
  name: 'myschema',
  type: 'document',
  title: 'My Schema',
  description: 'MySchema',
  fields: [
    {
      name: 'active',
      title: 'Active',
      type: 'boolean',
      components: {
        input: ExclusiveBooleanInput
      }
    },
    {
      name: 'name',
      title: 'Event Name',
      type: 'string'
    }
  ]
}

How It Works

  1. When an editor clicks the boolean toggle, the handleChange function is called
  2. If they're setting it to true, the component first queries for all other documents of type myschema where active is true (excluding the current document)
  3. It uses a transaction to batch update all those documents to set active: false
  4. Finally, it updates the current document's value using the onChange prop with a PatchEvent

This approach follows the patterns described in the Custom Input Components documentation, where custom components receive props like value, onChange, and access to the document id through the props object.

Important Considerations

  1. Real-time updates: The Studio will show the changes in real-time thanks to Sanity's collaborative features, but editors may need to refresh to see the deactivation of other documents if they have them open

  2. Permissions: Make sure the user has write permissions for all documents of this type, otherwise the transaction will fail

  3. Draft vs Published: This implementation updates both drafts and published versions. If you want different behavior, you'll need to handle draft IDs (which have drafts. prefix) separately

  4. Performance: If you have many documents, consider adding pagination or limiting the query scope

Alternative: Validation Rule

If you want a simpler approach that doesn't auto-update but prevents mistakes, you could add a custom validation rule that checks if another document is already active:

{
  name: 'active',
  title: 'Active',
  type: 'boolean',
  validation: Rule => Rule.custom(async (value, context) => {
    if (!value) return true // If false, no validation needed
    
    const {document, getClient} = context
    const client = getClient({apiVersion: '2024-01-01'})
    
    const otherActive = await client.fetch(
      `count(*[_type == "myschema" && active == true && _id != $id])`,
      {id: document._id}
    )
    
    return otherActive > 0 
      ? 'Another event is already active. Please deactivate it first.' 
      : true
  })
}

The custom input component approach gives the best user experience since it handles everything automatically for your editors!

Hi User. This approach might get more complicated as you consider other parts of the picture, such as changes made outside of the studio (e.g., using the mutations API) that will not adhere to validation or document actions in the studio. If I might make a suggestion, I wonder if you might consider a different way of setting the active document, which is to have a singleton (e.g., a settings page) with a reference to a document. That document is the one considered “active” for this purpose, and when someone wants to change it they do so on that settings page (without worrying about conflicts, unsetting first, etc.). This would also permit changes to be made via the CLI or API.
If you want to stick with your current boolean approach, one method might be using validation (check if any other documents have the boolean set to true, and prevent publication if so). Another might be
document actions , where you could unset the field in the other document when you publish this one.
Hi
user A
! Thanks for your response! This is my first time hearing about the singleton approach. Where can i find more information about it?
This is a good guide. Please let me know if you face any struggles and we’ll get your through them.
This is a good guide. Please let me know if you face any struggles and we’ll get your through them.
Great! Thank you!
This is a good guide. Please let me know if you face any struggles and we’ll get your through them.
I watched the video and it could work but i have question: If i create a 'Setting Page', does that mean that i will have to query the setting document instead of the event document?
Yes, you’d need to query it in some form. If your current query were:

*[_type == 'myschema' && active == true]
Your new one might be something like:


*[_type == 'myschema' && *[_type == 'settings' && active._ref == ^._id]]
Yes, you’d need to query it in some form. If your current query were:

*[_type == 'myschema' && active == true]
Your new one might be something like:


*[_type == 'myschema' && *[_type == 'settings' && active._ref == ^._id]]
That inner query would only return true in one instance—where the
_ref
property on the settings document’s
active
field is equivalent to the
_id
of the document being “considered” (as far as looping over documents goes).
Got it. I wasn't aware of the 'setting page' approach. That changes a lot of my schemas designs(luck me we are not in production with enough time to make those quick changes). THANK YOU User!!
You’re welcome! As I said, it’s not the only way to go, but I can anticipate problems with the boolean approach that are solved this way (and hopefully some time soon a release will make things even easier 😉).

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?