Can you run GROQ inside a Document Preview?

6 replies
Last updated: May 12, 2022
Hi all, anyone ever run some groq inside a document preview? Inside a reference field I need to add a flag to illustrate whether the referenced document also references the current document πŸ™ƒ.

preview: {
    select: {
      title: 'title',
      subtitle: 'type',
      type: 'type',
    },
    prepare({ title = 'Untitled', type, subtitle }) {
      const sub = // groq - does this doc reference the parent doc?;
      return {
        title,
        subtitle: sub,
        media: eosIcon,
      };
    },
  },
AI Update

Unfortunately, you cannot run GROQ queries directly inside the prepare function of a preview configuration. The prepare function is synchronous and doesn't have access to the Sanity client or the ability to make additional queries. This is a known limitation mentioned in the Sanity documentation.

However, you have a couple of alternatives to achieve what you're trying to do:

You can create a custom preview component that can use the Sanity client to fetch additional data:

import { PreviewProps } from 'sanity'
import { useClient } from 'sanity'
import { useEffect, useState } from 'react'

const CustomReferencePreview = (props: PreviewProps) => {
  const client = useClient({ apiVersion: '2024-01-01' })
  const [hasBackReference, setHasBackReference] = useState(false)
  const documentId = props.documentId
  const parentId = /* you'll need to pass this somehow */

  useEffect(() => {
    const checkBackReference = async () => {
      const result = await client.fetch(
        `*[_id == $documentId && references($parentId)][0]`,
        { documentId, parentId }
      )
      setHasBackReference(!!result)
    }
    
    checkBackReference()
  }, [documentId, parentId, client])

  return props.renderDefault({
    ...props,
    subtitle: hasBackReference ? '↔️ Mutual reference' : props.subtitle
  })
}

// In your schema
{
  name: 'myReference',
  type: 'reference',
  components: {
    preview: CustomReferencePreview
  }
}

Option 2: Pre-compute the Flag with Sanity Functions

If this relationship is important, consider adding a field that gets updated when the relationship changes. You could use Sanity Functions with a document mutation event handler to automatically update this flag whenever documents are linked:

// In sanity.blueprint.ts
export default defineBlueprint({
  functions: [
    {
      name: 'update-mutual-references',
      handler: './functions/updateMutualReferences.ts',
      on: {
        mutation: {
          documentTypes: ['yourDocumentType']
        }
      }
    }
  ]
})

Option 3: Select from a Computed Field

If you add a computed field to your document that tracks mutual references, you can select it in your preview:

preview: {
  select: {
    title: 'title',
    hasMutualReference: 'mutualReferenceFlag' // pre-computed field
  },
  prepare({ title, hasMutualReference }) {
    return {
      title,
      subtitle: hasMutualReference ? '↔️ Mutual' : 'One-way β†’'
    }
  }
}

The custom preview component (Option 1) is probably your best bet for dynamic checking, though be aware it will make queries for each preview rendered, which could impact performance with many items. The Sanity Functions approach (Option 2) would be more performant as it pre-computes the relationship, but requires more setup.

Alternative is creating a custom component. Although achievable its a lot of work for such a small tweak to the default
array
of
references
setup... if I could somehow add the flag to the preview then I can save having to re-invent the wheel!
Any ideas/approaches/help more than welcome, thanks!
Hey Mark! Previews can't handle async functions, so you won't be able to fetch anything from there. Is the parent referenced on the document? If so, you can select a value from that document using this method.
user M
- thank you soooo much for the pointers there, as ever amazing support!
I am so close to having it work perfectly, but, there is potentially one fatal issue.

Working code and how it renders attached... all perfect.

preview: {
    select: {
      title: 'title',
      subtitle: 'type',
      type: 'type',
      ref: 'relEos.0.relEos',
      id: '_id',
    },
    prepare({ title = 'Untitled', type, subtitle, ref, id }) {

      const references = ref ? ref : {};
      let bidirectional = false
      let refIds;
      if(!isEmpty(references)) {
        refIds = Object.entries(references).map(([key, value]) => value?._ref);
        bidirectional = refIds.includes(id);
      }

      return {
        title,
        subtitle: bidirectional ? `${subtitle.toUpperCase()} - Bidirectional` : subtitle.toUpperCase(),
      };
    },
  }
The issue is I need this for every item in the reference array... obviously by using
ref: 'relEos.0.relEos'
in the select I'm limited to the first array item.
Doing
ref: 'relEos'
returns an array of all the references but I can't then access the data in those referenced documents without groq.
So hacky pirate me is thinking I can just add 30
ref: 'relEos.0.relEos'
to my
select
and then just conditionally process them.
Sensible good dev me is thinking it does indeed require a custom component. I've had a little poke through the source code for the
reference
field and am thinking I could piece together a custom reference component along the lines of this . I've not done much with `part:`'s before. Thoughts or ideas very much appreciated 😊.
Ah, yeah that is an issue. I think at this point you will need to create a custom preview component, since it'll allow you to create a React component that can handle promises.
You can make custom preview components?! I love Sanity! Thanks for the direction you utter champion 🀘
Haha, you're welcome! It's a really similar process to creating a custom input component. More on it here .

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?