useDocuments

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.
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> )}
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. Suspense
in the React documentationimport { 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.
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.