How to resolve references for split pane preview in Sanity Studio?

3 replies
Last updated: May 18, 2023
Hello. I am trying to implement a preview in a split pane view in the studio and to accomplish the task, references need to be resolved. What is the best way of going about this? I have looked at User's sanity-plugin-utils and could not figure out what
SanityDocument
is...
AI Update

For implementing a split pane preview in Sanity Studio with resolved references, you'll want to use a combination of custom Component Views and GROQ queries with the reference operator (->). Let me walk you through the best approach.

Understanding SanityDocument

SanityDocument is a TypeScript type that represents any document in your Sanity Content Lake. When you run sanity typegen generate, it creates type definitions for all your schema types. The base SanityDocument type typically includes core fields like _id, _type, _createdAt, _updatedAt, and _rev.

If you're seeing SanityDocument in sanity-plugin-utils but can't figure out what it is, it's likely a generic type parameter. You'll want to use your own generated types from sanity typegen instead.

Implementing Split Pane Preview with Resolved References

Here's the recommended approach using Component Views:

1. Set up the Structure with Component View

import { StructureBuilder as S } from 'sanity/structure'
import PreviewPane from './components/PreviewPane'

export const structure = (S) =>
  S.list()
    .title('Content')
    .items([
      S.listItem()
        .title('Your Document Type')
        .child(
          S.documentTypeList('yourDocType')
            .child((documentId) =>
              S.document()
                .documentId(documentId)
                .schemaType('yourDocType')
                .views([
                  S.view.form(),
                  S.view.component(PreviewPane).title('Preview')
                ])
            )
        )
    ])

2. Fetch Data with Resolved References

In your PreviewPane component, use the useClient hook to fetch the document with references resolved using GROQ's reference operator (->):

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

export default function PreviewPane(props) {
  const { document } = props
  const client = useClient({ apiVersion: '2024-01-01' })
  const [resolvedDoc, setResolvedDoc] = useState(null)

  useEffect(() => {
    const query = `*[_id == $id][0]{
      ...,
      // Resolve single reference
      author->,
      // Resolve array of references
      categories[]->,
      // Resolve nested references with specific fields
      relatedPosts[]->{
        title,
        slug,
        author->
      }
    }`

    client.fetch(query, { id: document.displayed._id })
      .then(setResolvedDoc)
  }, [document.displayed._id, client])

  if (!resolvedDoc) return <div>Loading preview...</div>

  return (
    <div style={{ padding: '2rem' }}>
      <h2>{resolvedDoc.title}</h2>
      <p>Author: {resolvedDoc.author?.name}</p>
      {/* Render your preview with resolved data */}
    </div>
  )
}

3. Handle Real-time Updates

For a more robust solution that updates in real-time as you edit, you can use the client's listen() method:

useEffect(() => {
  const query = `*[_id == $id][0]{ ..., author->, categories[]-> }`
  
  const subscription = client.listen(query, { id: document.displayed._id })
    .subscribe((update) => {
      if (update.result) {
        setResolvedDoc(update.result)
      }
    })

  // Initial fetch
  client.fetch(query, { id: document.displayed._id })
    .then(setResolvedDoc)

  return () => subscription.unsubscribe()
}, [document.displayed._id, client])

Key Points About Reference Resolution

The -> operator is powerful for resolving references in GROQ:

  • Single references: author-> resolves to the full author document
  • Array references: categories[]-> resolves all category references
  • Selective fields: author->{name, bio} gets only specific fields
  • Nested resolution: relatedPosts[]->{ title, author-> } resolves multiple levels

Keep in mind that each -> performs a subquery internally, so be mindful of performance when resolving deeply nested or large arrays of references.

TypeScript Types

If you want full type safety, generate types with sanity typegen:

npx sanity@latest schema extract
npx sanity@latest typegen generate

Then import and use your generated types:

import type { YourDocType } from './sanity.types'

const [resolvedDoc, setResolvedDoc] = useState<YourDocType | null>(null)

This approach gives you a clean split pane view with fully resolved references, updating in real-time as you edit your document.

Show original thread
3 replies
The split plane is a React component, so you can resolve the references using the
useClient
hook within it. More on that here .
woot thanks
user M
! I was trying to figure out what i need as the way I had done it in the v2 sanity has been deprecated.
You’re very 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?