Read comments
Read the comments on one document with the App SDK, as a list or as threads, and handle loading and disconnected states.
This guide reads the comments on one document, as a flat list or grouped into threads, narrows them by field and status, and handles the three states a live list can be in: loading, switching, and disconnected.
Prerequisites:
- An app with
@sanity/sdk-react3.11.0 or later set up. See Setup. - A document handle: a
documentIdand adocumentType.
Choose between comments and threads
Two hooks read the same list:
useCommentsreturns every comment on the document, newest first, replies included. It suits a count or a feed.useCommentThreadsreturns the same comments grouped into threads, newest thread first. Each thread carries its first comment asparentCommentand itsreplies, oldest first.
Both take the same options and read the same list, so using them side by side costs one connection, not two.
import {useCommentThreads} from '@sanity/sdk-react/collaboration'
function ThreadCount({documentId}: {documentId: string}) {
const {threads} = useCommentThreads({documentId, documentType: 'article'})
return <span>{threads.length}</span>
}Narrow by field and status
fieldPath narrows to one field, as a string such as 'title' or a Path array. status is 'open' or 'resolved'. Leave either out for everything.
const {threads} = useCommentThreads({
documentId,
documentType: 'article',
fieldPath: 'title',
status: 'open',
})The two hooks read these filters slightly differently:
- Threads take
statusandfieldPathfrom their first comment, so the filter selects whole threads. A resolved parent brings its replies with it. - Comments have nothing to bring along, so each comment is judged on its own.
fieldPath: '' selects the comments on the document as a whole. See Comment on the whole document.
Read from drafts, published, or releases
A document can exist as a published document, a draft, and a version per release. scope decides which of those the comments are gathered from:
| Reads |
|---|---|
| What you are viewing: a release's own comments, or draft and published pooled together |
| Only the document ID you passed |
| Every comment on the document, in any form |
To read the pooled draft and published list while a release is in view, pass a non-release perspective alongside, such as perspective: 'drafts'. To name a release yourself, pass its version ID as the documentId.
A content variant is stored as a version document, so under 'perspective' its comments form a list of their own, the way a release's do. A variant's draft and published comments are separate lists.
Wrap the read in Suspense
Both hooks suspend until the comments have loaded, so put a Suspense boundary above them:
import {Suspense} from 'react'
function DocumentComments({documentId}: {documentId: string}) {
return (
<Suspense fallback={<p>Loading comments…</p>}>
<Threads documentId={documentId} />
</Suspense>
)
}Switching to a different document or filter happens in a transition instead. The list already on screen stays put and isPending goes true until the new one arrives:
import {useCommentThreads} from '@sanity/sdk-react/collaboration'
function Threads({documentId}: {documentId: string}) {
const {threads, isPending} = useCommentThreads({
documentId,
documentType: 'article',
})
return (
<ul data-pending={isPending}>
{threads.map((thread) => (
<li key={thread.threadId}>{thread.commentsCount} comments</li>
))}
</ul>
)
}Show a dropped connection
Comments arrive over a live connection, and that connection can drop, usually because a token expired. A list that has already loaded is not thrown away when that happens. It stays on screen as it last stood, and error is set to say it has stopped following the server:
import {useCommentThreads} from '@sanity/sdk-react/collaboration'
function Threads({documentId}: {documentId: string}) {
const {threads, error} = useCommentThreads({
documentId,
documentType: 'article',
})
return (
<>
{error ? <output>Comments are not up to date.</output> : null}
<ul>
{threads.map((thread) => (
<li key={thread.threadId}>{thread.commentsCount} comments</li>
))}
</ul>
</>
)
}error clears on its own once a connection comes back, which follows the next token refresh.
A connection that fails before the first comments have loaded is different: there is nothing to keep showing, so the hook throws to the nearest error boundary.
Hide threads on removed fields
Unlike Sanity Studio, the SDK returns every thread it finds. The studio hides threads whose field has left the schema or is hidden by a conditional, which it can do because it has the schema. To do the same, check fieldPath against the fields you know about. A nested path such as body[_key=="a1"].children starts with its field name and a [:
import {useCommentThreads} from '@sanity/sdk-react/collaboration'
// The fields your schema has. `''` keeps comments on the whole document.
const knownFields = ['', 'title', 'body']
function VisibleThreads({documentId}: {documentId: string}) {
const {threads} = useCommentThreads({documentId, documentType: 'article'})
const visible = threads.filter((thread) =>
knownFields.some(
(field) =>
thread.fieldPath === field || thread.fieldPath.startsWith(`${field}[`),
),
)
return <span>{visible.length}</span>
}Next steps
- Comments hook reference, for the full signatures of
useCommentsanduseCommentThreads. - Query comments, for anything that is not one document's comments.
- Show comment counts, which builds badges from this read.
Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?