React hooks
Meet some of the most important hooks from the React SDK package.
The Sanity App SDK comes with a range of hooks for interacting with your content. For a full reference, see:
In this article, you'll learn what the most important App SDK hooks for reading and changing documents do, with a short example of each. Covering every hook, type, and component is beyond the scope of this article.
For the sake of legibility, assume that examples handling single documents are invoked with a proper DocumentHandle, which is a valid combination of a document ID and document type, and an optional project ID and dataset name indicating the source of the document. Examples that fetch multiple documents usually return an array of DocumentHandles.
import {type DocumentHandle} from '@sanity/sdk-react'
const documentHandle: DocumentHandle = {
documentId: 'document-id',
documentType: 'book',
projectId: '<your-project-id>',
dataset: 'production',
}Prerequisites
@sanity/sdk-react3.0.0 or later, with React 19.2 or later. The examples in this article import hooks and types from this package.- A React app set up with the App SDK, with components that call these hooks rendered inside
<SanityApp>. Outside it, the hooks throwSanityInstance context not found. Please ensure that your component is wrapped in a ResourceProvider or a SanityApp component.To create an app, follow the App SDK quick start.
Data retrieval hooks
Get collections of documents
The useDocuments hook is your primary tool for retrieving collections of documents from your Sanity dataset. It returns document handles for documents matching your specified document type (and optional filters and parameters), making it ideal for building document lists and overviews.
import {useDocuments} from '@sanity/sdk-react'
const {data, hasMore, isPending, loadMore} = useDocuments({
documentType: 'movie',
batchSize: 10,
orderings: [{ field: '_createdAt', direction: 'desc' }]
})Key options for useDocuments are documentType (a string or an array of strings, optional), batchSize (number, optional: how many handles to load per batch, defaults to 25), and orderings (optional, array of {field, direction: 'asc' | 'desc'}). For the full list of options and return values, see the useDocuments reference. The key return values are:
data: an array ofDocumentHandles for the matching documentshasMore:truewhile more documents remain beyond the current batchisPending:truewhile the next batch is loadingloadMore: call this (for example, from a "Load more" button) to append the next batch todata
Use these for infinite-scroll or "load more" lists; for numbered pages use usePaginatedDocuments.
Paginate document lists
The usePaginatedDocuments hook provides a more traditional pagination interface compared to the infinite scroll pattern of useDocuments. This makes it ideal for building interfaces with discrete pages of content and explicit navigation controls:
import {usePaginatedDocuments} from '@sanity/sdk-react'
const {
data,
isPending,
currentPage,
totalPages,
nextPage,
previousPage,
hasNextPage,
hasPreviousPage
} = usePaginatedDocuments({
documentType: 'movie',
pageSize: 10,
orderings: [{ field: '_createdAt', direction: 'desc' }]
})Key options for usePaginatedDocuments are documentType (a string or an array of strings, optional), pageSize (number of documents per page, defaults to 25), and orderings (array of {field, direction}). For the full list of options and return values, see the usePaginatedDocuments reference. The key return values are:
data: theDocumentHandles for the current pageisPending:truewhile a page is loadingcurrentPage/totalPages: the current page number (starting at 1) and total page countnextPage/previousPage: functions to move between pageshasNextPage/hasPreviousPage: booleans for enabling and disabling navigation controls
Read individual documents
The useDocument hook provides real-time access to individual document content. It's designed for reading and subscribing to a document's state, incorporating both local and remote changes:
import {useDocument} from '@sanity/sdk-react'
// Get the full document
const {data: movie} = useDocument({...movieHandle})
// Get a specific field
const {data: title} = useDocument({
...movieHandle,
path: 'title',
})The hook automatically handles displaying local-first, optimistic updates made with the useEditDocument hook, making it ideal for building collaborative editing interfaces that need to stay synchronized with remote changes. However, for static displays where local-first, optimistic updates aren't needed, consider using useDocumentProjection (which still returns content that's live by default).
Access specific document fields
The useDocumentProjection hook lets you efficiently retrieve specific fields from a document using GROQ projections:
import {useDocumentProjection} from '@sanity/sdk-react'
const {data: { title, authorName }} = useDocumentProjection({
...documentHandle,
projection: `{
title,
'authorName': author->name
}`
})Alongside a DocumentHandle, useDocumentProjection accepts projection (a GROQ projection string, for example {title, 'authorName': author->name}) and an optional ref (a React ref to an element; the hook won't resolve while that element is offscreen). It returns {data, isPending}, where data holds the projected fields. Because it suspends while fetching, call it inside a component wrapped in a <Suspense> boundary, typically one rendered per DocumentHandle from useDocuments.
Putting these together, useDocuments for the list, a per-item <Suspense> boundary, and useDocumentProjection for the titles:
import {Suspense} from 'react'
import {
useDocuments,
useDocumentProjection,
type DocumentHandle,
} from '@sanity/sdk-react'
function DocumentTitle({documentHandle}: {documentHandle: DocumentHandle}) {
const {data} = useDocumentProjection({
...documentHandle,
projection: `{title}`,
})
return <li>{data.title}</li>
}
export function MovieList() {
const {data, hasMore, isPending, loadMore} = useDocuments({
documentType: 'movie',
batchSize: 10,
orderings: [{field: '_createdAt', direction: 'desc'}],
})
return (
<>
<ul>
{data.map((documentHandle) => (
<Suspense key={documentHandle.documentId} fallback={<li>Loading…</li>}>
<DocumentTitle documentHandle={documentHandle} />
</Suspense>
))}
</ul>
{hasMore && (
<button onClick={() => loadMore()} disabled={isPending}>
{isPending ? 'Loading…' : 'Load more'}
</button>
)}
</>
)
}Document manipulation hooks
Modify documents
The useEditDocument hook is particularly useful for building forms and collaborative editing interfaces. It lets you update document fields in real time:
import {useDocument, useEditDocument, type DocumentHandle} from '@sanity/sdk-react'
function MovieTitleInput({movieHandle}: {movieHandle: DocumentHandle}) {
const {data: title} = useDocument<string, 'title'>({
...movieHandle,
path: 'title',
})
const editTitle = useEditDocument({
...movieHandle,
path: 'title',
})
function handleTitleChange(e: React.ChangeEvent<HTMLInputElement>) {
editTitle(e.currentTarget.value)
}
return (
<input
type="text"
value={title || ''}
onChange={handleTitleChange}
/>
)
}Perform document operations
The useApplyDocumentActions hook provides a way to perform document operations like publishing, unpublishing, creating, and deleting documents:
import {
useApplyDocumentActions,
publishDocument,
unpublishDocument,
type DocumentHandle,
} from '@sanity/sdk-react'
function MovieActions({movieHandle}: {movieHandle: DocumentHandle}) {
const apply = useApplyDocumentActions()
return (
<div>
<button onClick={() => apply(publishDocument(movieHandle))}>
Publish
</button>
<button onClick={() => apply(unpublishDocument(movieHandle))}>
Unpublish
</button>
</div>
)
}Handle document events
The useDocumentEvent hook lets you subscribe to events from your app's document store, such as documents being created, edited, published, or deleted. These events fire when an action submitted from your app is accepted, so use them to react to your app's own changes, for example to confirm that an edit or publish went through:
import {useDocumentEvent, type DocumentEvent} from '@sanity/sdk-react'
const eventCallback = (event: DocumentEvent) => {
if (event.type === 'deleted') {
console.log(`Document ${event.documentId} was deleted`)
} else if (event.type === 'edited') {
console.log(`Document ${event.documentId} was edited`)
}
}
useDocumentEvent({onEvent: eventCallback})The hook isn't scoped to a single document. It receives events for every document in the dataset it listens to, and each event includes a documentId. To choose that dataset, pass an optional projectId and dataset (or a resource); without them, the hook uses your app's default dataset. Changes made by other users or clients don't produce 'edited' or 'published' events. They arrive as 'remote-patches' events instead.
Here's an example of using useDocumentEvent to build a notification feed that lists the edits and publishes your app makes:
import {useState} from 'react'
import {useDocumentEvent, type DocumentEvent} from '@sanity/sdk-react'
function DocumentChangeNotifier() {
const [notifications, setNotifications] = useState<string[]>([])
const eventCallback = (event: DocumentEvent) => {
switch (event.type) {
case 'edited':
setNotifications(prev => [
`Document ${event.documentId} was just edited`,
...prev
])
break
case 'published':
setNotifications(prev => [
`Document ${event.documentId} was published`,
...prev
])
break
}
}
useDocumentEvent({onEvent: eventCallback})
return (
<div className="notifications">
{notifications.map((msg, i) => (
<div key={i} className="notification">{msg}</div>
))}
</div>
)
}Next steps
Keep building with these resources:
- To use these hooks in a running app, follow Fetching and handling content.
- To learn how handles pass between list and detail hooks, read Document handles.
- To look up every hook, option, and return value, see the App SDK React reference.
Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?