Lesson
4
useDocuments
Log in to watch a video walkthrough of this lesson
Log in

Performant querying for a live-updating list of documents has never been simpler.
Log in to mark your progress for each Lesson and Task
Maybe the most basic thing we can do when creating an application is query and render a list of documents. In this lesson, you'll do it using the useDocuments
hook.
Before we do, let's interrogate this decision.
Sanity Client is the primary way JavaScript applications are built to interact with Sanity's APIs. The App SDK is a wrapper of Sanity Client for building apps and solves much of the UI complexity that comes with working with a lower-level client.
For example, when you query documents with client.fetch
, the list of documents will not update in real-time as documents change. You might also unknowingly fetch 10,000 documents. Edits made to these documents will not automatically create per-render optimistic updates in the UI.
All complexity and performance concerns are bundled up in the App SDK's React hooks, making it simpler and faster for us to build better content applications.
The App SDK provides the useQuery hook, which takes a GROQ query. We could use this hook for all our data fetching. However, many other hooks in the App SDK for React require "document handles" to be passed in as parameters.
These can be created ad hoc from values in a document. Still, it's simpler to retrieve document handles in a parent component and pass them down to child components, which perform fetches or actions using those handles.
There are some patterns where it makes more sense to use useQuery
instead of useDocuments
, such as when a parent component needs to know specific values of each document. For example, a component may need to know which day an event is on to render it into a calendar or the geolocation of an event to plot it on a map.
In these instances, you may be better off fetching with useQuery
documents and their values in the parent component. Just be aware that this can lead to overfetching.
A document handle contains at least two and up to four useful pieces of information about a document to identify its type and origin. By keeping the data returned by fetches of documents smaller, we maintain focus on improved performance in our applications.
{ "dataset": "production", "documentId": "116d2c7a-d1de-4d00-9a88-8ac65ceaad10", "documentType": "feedback", "projectId": "xe385msc"}
In Server-Side Rendered (SSR) web application frontends, you have likely formed a habit of fetching everything your web page needed in one huge GROQ query using client.fetch
.
This isn't necessary in a Single Page Application (SPA).
The happy path for App SDK apps is to filter for specific documents using useDocuments
and pass down the returned document handles to components which individually fetch, edit and take actions on documents.
Any concerns about caching, real-time and optimistic updates are all taken care of by the App SDK.
Create a new component called
Feedback
, which will be the parent component of all our UI.import { Suspense, useState } from "react"import { DocumentHandle } from "@sanity/sdk-react"import { Card, Flex, Grid, Spinner } from "@sanity/ui"import { styled } from "styled-components"
import { FeedbackList } from "./FeedbackList"
const ScreenHeightCard = styled(Card)` height: 100vh; overflow: scroll;`
export function Feedback() { const [selectedFeedback, setSelectedFeedback] = useState<DocumentHandle | null>(null)
return ( <Grid columns={5}> <ScreenHeightCard columnStart={1} columnEnd={3}> <Suspense fallback={<Loading />}> <FeedbackList setSelectedFeedback={setSelectedFeedback} selectedFeedback={selectedFeedback} /> </Suspense> </ScreenHeightCard> <ScreenHeightCard borderLeft columnStart={3} columnEnd={6}> {/* TODO: Add <FeedbackEdit /> form */} </ScreenHeightCard> </Grid> )}
function Loading() { return ( <Flex justify="center" align="center" width="fill" height="fill"> <Spinner /> </Flex> )}
Don't forget your
Suspense
boundaries. The App SDK React Hooks use Suspense for data fetching. This means any component which uses one of these hooks could cause a re-render further up the component tree. Since this Feedback
component will be rendering both the FeedbackList
and FeedbackEdit
form components, without being wrapped in Suspense
an update in one component would force both to re-render. Read more about
Suspense
in the React documentationCreate another component to query for the feedback documents.
import { type DocumentHandle, useDocuments } from "@sanity/sdk-react"import { Stack, Button } from "@sanity/ui"
type FeedbackListProps = { selectedFeedback: DocumentHandle | null setSelectedFeedback: (feedback: DocumentHandle | null) => void}
export function FeedbackList({ selectedFeedback, setSelectedFeedback,}: FeedbackListProps) { const { data, hasMore, loadMore } = useDocuments({ documentType: "feedback", })
return ( <Stack space={2} padding={5}> {data?.map((feedback) => ( <pre key={feedback.documentId}>{JSON.stringify(feedback, null, 2)}</pre> ))} {hasMore && <Button onClick={loadMore} text="Load more" />} </Stack> )}
Lastly, you'll need to load the Feedback component into the main App.
Update
App.tsx
to replace ExampleComponent
with Feedback
import {Feedback} from "./Feedback"
export default function App() { // ...sanityConfigs, Loading
return ( <SanityUI> <SanityApp config={sanityConfigs} fallback={<Loading />}> <Feedback /> </SanityApp> </SanityUI> )}
In your application you should now see a list of document handles rendered into the UI.
So you now have an application that can query documents, but not any useful information about them. Let's fix that in the next lesson.
You have 3 uncompleted tasks in this lesson
0 of 3