|
| 1 | +--- |
| 2 | +title: Introducing Document Handles |
| 3 | +--- |
| 4 | + |
| 5 | +# Introducing Document Handles |
| 6 | + |
| 7 | +Document Handles ({@link DocumentHandle see their type definition here}) are a new concept introduced by the Sanity App SDK, and are important to understand when working with many of the SDK’s React hooks. In this guide, we’ll describe what Document Handles are, why they’re useful, and how to work with them. |
| 8 | + |
| 9 | +## What is a Document Handle? |
| 10 | + |
| 11 | +In short, a Document Handle is a ‘stub’ of a document — a small piece of metadata, encoded in a JavaScript object, that acts as a reference to a complete document in your dataset(s). |
| 12 | + |
| 13 | +It looks like this: |
| 14 | + |
| 15 | +```JavaScript |
| 16 | +const myDocumentHandle = { |
| 17 | + _id: 'my-document-id', |
| 18 | + _type: 'article' |
| 19 | +} |
| 20 | +``` |
| 21 | + |
| 22 | +A Document Handle may also contain a resource ID; in that case, it would look like this: |
| 23 | + |
| 24 | +```JavaScript |
| 25 | +const myDocumentHandle = { |
| 26 | + _id: 'my-document-id', |
| 27 | + _type: 'author', |
| 28 | + resourceId: `document:${projectId}.${dataset}:${documentId}` |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +Therefore, for a document in a given dataset that looks (in part) like this: |
| 33 | + |
| 34 | +```JSON |
| 35 | +{ |
| 36 | + "_id": "123456-abcdef", |
| 37 | + "_type": "book", |
| 38 | + "title": "Into the Cool", |
| 39 | + "publisher": "The University of Chicago Press", |
| 40 | + "pages": 378, |
| 41 | + "…": "…" |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +…the corresponding Document Handle would look like this: |
| 46 | + |
| 47 | +```JavaScript |
| 48 | +{ |
| 49 | + _id: "123456-abcdef", |
| 50 | + _type: "book" |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +## Why are Document Handles used? |
| 55 | + |
| 56 | +Hooks like {@link useDocuments} and {@link usePaginatedDocuments} can return potentially large numbers of documents matching your specified parameters. If these hooks were to return every matching document in its entirety, this could end up being a potentially performance heavy operation, which could thus slow down your application and result in a poor user experience. Additionally, you may not need each returned document in its entirety to begin with — perhaps, for example, you just want to render a document preview, or one or two arbitrary fields of a document, or to simply get a count of documents matching your parameters. |
| 57 | + |
| 58 | +This is where the concept of Document Handles comes in. By returning a small amount of metadata for each document instead of unfurling every returned document, hooks like {@link useDocuments} can respond as fast as possible, allowing your application to remain snappy. |
| 59 | + |
| 60 | +Of course, unless you’re just looking to get a count of documents matching the parameters you pass to these hooks, Document Handles aren't incredibly useful on their own. This is by design — they’re only meant to serve as references to documents which can then be consumed by more specialized hooks, such as {@link useProjection}, {@link useDocument}, and many more hooks provided by the SDK. These specialized hooks are designed to consume document handles and emit only the document content you request, which also delivers huge performance benefits. Other hooks, such as {@link useDocumentEvent} and {@link useDocumentPermissions} have no need to know the contents of a document — instead, they use the provided Document Handle to reference a document and retrieve information pertaining to that document. |
| 61 | + |
| 62 | +In short, Document Handles promote deferring the retrieval of document contents until such time as those contents are actually needed by your application. |
| 63 | + |
| 64 | +## Using your own Document Handles |
| 65 | + |
| 66 | +You’re not limited to using Document Handles returned by hooks like {@link useDocuments} — if it suits your use case (for example: if you know the document ID and type of the document you want to reference), you can certainly write and use your own Document Handles. |
| 67 | + |
| 68 | +For example: |
| 69 | + |
| 70 | +```tsx |
| 71 | +import {useDocumentSyncStatus, type DocumentHandle} from '@sanity/sdk-react' |
| 72 | + |
| 73 | +const myDocumentHandle: DocumentHandle = { |
| 74 | + _id: 'my-document-id', |
| 75 | + _type: 'book', |
| 76 | +} |
| 77 | + |
| 78 | +const documentSynced = useDocumentSyncStatus(myDocumentHandle) |
| 79 | +``` |
| 80 | + |
| 81 | +## A quick example |
| 82 | + |
| 83 | +Let’s say you’d like to get all of the documents of type `'author'` from a dataset. In your Sanity application, you could use the {@link useDocuments} hook to do that: |
| 84 | + |
| 85 | +```tsx |
| 86 | +import {useDocuments} from '@sanity/sdk' |
| 87 | + |
| 88 | +export function AuthorList() { |
| 89 | + const {data: authors} = useDocuments({filter: '_type: "author"'}) |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +At this point, the `authors` variable contains an array of Document Handles, which, because we’re filtering for only the `author` content type, will look like this: |
| 94 | + |
| 95 | +```tsx |
| 96 | +{ _id: 'the-document-id', _type: 'author' } |
| 97 | +``` |
| 98 | + |
| 99 | +With this information, we could render the number of authors in the dataset — for example: |
| 100 | + |
| 101 | +```tsx |
| 102 | +import {useDocuments} from '@sanity/sdk' |
| 103 | + |
| 104 | +export function AuthorList() { |
| 105 | + const {data: authors} = useDocuments({filter: '_type: "author"'}) |
| 106 | + |
| 107 | + return <p>There are currently {authors.length} authors in our dataset.</p> |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +If we wanted to instead render content from each of these documents — for example, the author’s name — we’d then need to pass each Document Handle to a different hook — for example, {@link useProjection}: |
| 112 | + |
| 113 | +```tsx |
| 114 | +import {useProjection, type DocumentHandle} from '@sanity/sdk' |
| 115 | + |
| 116 | +interface ProjectionResult { |
| 117 | + results: { |
| 118 | + name: string |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +// The AuthorDetails component will accept a Document Handle for its `document` prop |
| 123 | +export function AuthorDetails({document}: {document: DocumentHandle}) { |
| 124 | + const {results}: ProjectionResult = useProjection({ |
| 125 | + document, |
| 126 | + projection: '{ name }', |
| 127 | + }) |
| 128 | + |
| 129 | + return <p>The author's name is {results.name}</p> |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +We can then use our `AuthorDetails` component with our previously created `AuthorList` component, and pass along the Document Handles to each instance of the `AuthorDetails` component: |
| 134 | + |
| 135 | +```tsx |
| 136 | +import {useDocuments} from '@sanity/sdk' |
| 137 | + |
| 138 | +import AuthorDetails from './AuthorDetails.tsx' |
| 139 | + |
| 140 | +export function AuthorList() { |
| 141 | + const { data: authors } = useDocuments({ filter: '_type: "author"'}) |
| 142 | + |
| 143 | + return ( |
| 144 | + <> |
| 145 | + <p>There are {authors.length} authors in our dataset! Here they are:</p> |
| 146 | + <ul> |
| 147 | + {authors.map(author => ( |
| 148 | + <li key={author._id}> |
| 149 | + <AuthorDetails document={author} /> |
| 150 | + </li> |
| 151 | + )} |
| 152 | + </ul> |
| 153 | + </> |
| 154 | + ) |
| 155 | +} |
| 156 | +``` |
| 157 | + |
| 158 | +We’ve now both retrieved a list of Document Handles, and used each of them in a dedicated component with a hook that consumes Document Handles. No matter how many authors there are in our dataset, nor how many fields might exist on our author type, this will keep our application performing as fast as possible by separating the concerns of retrieving author type documents (or rather, Document Handles) and retrieving data from those documents. |
| 159 | + |
| 160 | +``` |
| 161 | + |
| 162 | +``` |
0 commit comments