From 474b6b4592f0d67624e18d7f31356f2c67c6585b Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Mon, 22 Jan 2024 17:34:59 -0700 Subject: [PATCH] Add section about using preloadQuery --- docs/source/data/suspense.mdx | 130 +++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 3 deletions(-) diff --git a/docs/source/data/suspense.mdx b/docs/source/data/suspense.mdx index a479b8a7a6e..0ad226d07af 100644 --- a/docs/source/data/suspense.mdx +++ b/docs/source/data/suspense.mdx @@ -447,8 +447,9 @@ Starting with Apollo Client `3.9.0`, you can use `useLoadableQuery` to start loa Let's update our example to start loading the dog's details as a result of selecting from a dropdown. -```tsx -function App({ dogs }) { +```tsx {3,8,17,24} +function App() { + const { data } = useSuspenseQuery(GET_DOGS_QUERY); const [queryRef, loadDog] = useLoadableQuery(GET_DOG_QUERY) return ( @@ -456,7 +457,7 @@ function App({ dogs }) { loadDog({ id: e.target.value })} + > + {data.dogs.map(({ id, name }) => ( + + ))} + + Loading...}> + + + + ); +} + +const root = createRoot(document.getElementById('app')); + +root.render( + + Loading...}> + + + +); +``` + +We begin loading data as soon as our `preloadQuery` function is called. We no longer need to wait for React to render our `` component before our `GET_DOGS_QUERY` begins loading, but still get the benefit of suspense! + +#### Usage with routers + +Popular routers such as [React Router](https://reactrouter.com/en/main) and [TanStack Router](https://tanstack.com/router) include APIs to start loading data before the route component renders (e.g. such as the [`loader` function](https://reactrouter.com/en/main/route/route#loader) from React Router). These APIs have the benefit that they can begin loading data in parallel without the need to wait for the route component to render. This is especially useful for nested routes where a parent route would otherwise suspend and create request waterfalls for child routes. + +The `preloadQuery` API pairs nicely with these router APIs as it lets you take advantage of the optimizations these routers provide without sacrificing the ability to respond to cache updates in your route components. + +Let's update our example using React Router to begin loading data via a `loader` function. + +```ts +import { useLoaderData } from 'react-router-dom'; + +export function loader() { + return preloadQuery(GET_DOGS_QUERY); +} + +export function RouteComponent() { + const queryRef = useLoaderData(); + const { data } = useReadQuery(queryRef); + + return ( + // ... + ); +} +``` + +> NOTE: The `loader` function is only available in React Router versions 6.4 and above. + +We load our query as soon as React Router calls our `loader` function. This starts fetching before our route component is rendered. When our route component renders, we can get access to the `queryRef`, which is passed to `useReadQuery` to read the data. This means our route component has the ability to update itself when our cache is updated! + + + +This API only works in client-side routing. The `queryRef` returned from `preloadQuery` is not serializable across the wire and as such, will not work with routers that render on the server (e.g. such as [Remix](https://remix.run/)). + + + +#### Waiting for route transitions until the query is loaded + +By default, `preloadQuery` works similar to a [deferred loader](https://reactrouter.com/en/main/guides/deferred) in that routes will transition immediately and suspend with `useReadQuery` until the query is loaded. You may instead want to prevent the route from transitioning until the data is fully loaded. Use the `toPromise` method on the `queryRef` to get a promise that resolves when the query is loaded. This promise resolves with the `queryRef` itself, making it easy to use with hooks such as `useLoaderData`. + +```ts +export async function loader() { + const queryRef = await preloadQuery(GET_DOGS_QUERY).toPromise(); + + return queryRef; +} + +// You may also return the promise directly from loader. +// This is equivalent to the above. +export async function loader() { + return preloadQuery(GET_DOGS_QUERY).toPromise(); +} + +export function RouteComponent() { + const queryRef = useLoaderData(); + const { data } = useReadQuery(queryRef); + + // ... +} +``` + +This instructs React Router to wait for the query to finish loading before the route transitions. When the route does transition, the data will be rendered immediately without the need to show a loading fallback in the route component. + +#### Why prevent access to `data` in `toPromise`? + +You may be wondering why we resolve `toPromise` with the `queryRef` itself, rather than the data loaded from the query. This is because we want to encourage you to leverage `useReadQuery` to avoid missing out on cache updates for your query. If `data` were available, it would be tempting to consume it directly in your loader components. Doing so means missing out on cache updates. + +If you need access to raw query data in your `loader` functions, use [`client.query()`](../api/core/ApolloClient#query) directly. + ### Refetching and pagination Apollo's Suspense data fetching hooks return functions for refetching query data via the `refetch` function, and fetching additional pages of data via the `fetchMore` function.