Can we do asynchronous data fetching in our preview.prepare statement?

3 replies
Last updated: Sep 14, 2023
Can we do asynchronous data fetching in our preview.prepare statement?
We have run into an interesting case with work where we need to do this in certain situations and we are having a hard time with it
AI Update

Unfortunately, no, the preview.prepare function cannot be asynchronous. This is a documented limitation in Sanity Studio – the prepare function is designed to be synchronous and cannot make custom GROQ queries or perform async operations like API calls.

However, there are several alternative approaches you can use depending on your specific use case:

For scenarios requiring async data fetching, you can create a custom preview component using Studio's component API. This allows you to use React hooks like useClient to fetch data asynchronously:

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

const CustomPreview = (props) => {
  const client = useClient({apiVersion: '2024-01-01'})
  const [asyncData, setAsyncData] = useState(null)
  
  useEffect(() => {
    // Perform your async operations here
    client.fetch('*[_type == "someType"][0]')
      .then(data => setAsyncData(data))
  }, [props.documentId])
  
  return props.renderDefault({
    ...props,
    title: asyncData?.title || props.title,
    subtitle: asyncData?.customField || 'Loading...'
  })
}

// In your schema
export default {
  name: 'myDocument',
  type: 'document',
  components: {
    preview: CustomPreview
  }
}

Alternative 2: Pre-compute Values with Sanity Functions

If you're fetching data that could be computed and stored in your document, consider:

  1. Using Sanity Functions to listen for document changes and update computed fields
  2. Storing the async data as a field in your document that gets updated via a function
  3. Then accessing that pre-computed field in your standard preview.prepare

This is the modern, recommended approach for automation and event handling in Sanity – Functions are serverless compute handlers that run within Sanity's infrastructure, making them ideal for reacting to content changes without needing external hosting.

Alternative 3: Use Select with References

If you're trying to access data from referenced documents, you can often use the select object with dot notation to access referenced fields synchronously:

preview: {
  select: {
    title: 'title',
    authorName: 'author.name',  // Access referenced author's name
    categoryTitle: 'category.title'
  },
  prepare({title, authorName, categoryTitle}) {
    return {
      title: title,
      subtitle: `By ${authorName} in ${categoryTitle}`
    }
  }
}

Important Context from the Community

Based on discussions in the Sanity community, if you need to access the Sanity client in other schema functions (like initialValue or isUnique), you can get it from the context parameter:

initialValue: async (props, context) => {
  const {getClient} = context
  const client = getClient({ apiVersion: '2024-01-01'})
  // Use client here
}

// For isUnique, the client is passed directly:
isUnique: async (value, {client}) => {
  const slugs = await client.fetch(`*[defined(slug)].slug.current`)
  return !slugs.includes(value)
}

Note: The hidden property doesn't support promises, so if you need conditional visibility based on async data, you'll need to use a custom field component with conditional rendering instead.

The custom preview component approach (Alternative 1) is typically the best solution when you genuinely need async operations in previews, as it gives you full React capabilities including hooks and async/await. The trade-off is slightly more complexity, but it's the officially supported way to handle these scenarios in Studio v3.

No, you need to use a custom preview component if you need to do anything async.
Thanks!
You’re welcome!

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?