diff --git a/docs/source/data/fragments.mdx b/docs/source/data/fragments.mdx index d8f4fdf4373..4124d0eb53a 100644 --- a/docs/source/data/fragments.mdx +++ b/docs/source/data/fragments.mdx @@ -561,7 +561,7 @@ function Item(props: { item: { __typename: 'Item', id: number }}) { const { complete, data } = useFragment({ fragment: ItemFragment, fragmentName: "ItemFragment", - from: item + from: props.item }); return
  • {complete ? data.text : "incomplete"}
  • ; @@ -573,7 +573,7 @@ function Item(props) { const { complete, data } = useFragment({ fragment: ITEM_FRAGMENT, fragmentName: "ItemFragment", - from: item + from: props.item }); return
  • {complete ? data.text : "incomplete"}
  • ; @@ -589,6 +589,71 @@ function Item(props) { See the [API reference](../api/react/hooks#usefragment) for more details on the supported options. + +## `useSuspenseFragment` + + +For those that have integrated with React [Suspense](https://react.dev/reference/react/Suspense), `useSuspenseFragment` is available as a drop-in replacement for `useFragment`. `useSuspenseFragment` works identically to `useFragment` but will suspend while `data` is incomplete. + +Let's update the example from the previous section to use `useSuspenseFragment`. First, we'll update our `Item` component and replace `useFragment` with `useSuspenseFragment`. Since we are using Suspense, we no longer have to check for a `complete` property to determine if the result is complete because the component will suspend otherwise. + +```tsx +import { useSuspenseFragment } from "@apollo/client"; + +function Item(props) { + const { data } = useSuspenseFragment({ + fragment: ITEM_FRAGMENT, + fragmentName: "ItemFragment", + from: props.item + }); + + return
  • {data.text}
  • ; +} +``` + +Next, we'll will wrap our `Item` components in a `Suspense` boundary to show a loading indicator if the data from `ItemFragment` is not complete. Since we're using Suspense, we'll replace `useQuery` with `useSuspenseQuery` as well: + +```tsx +function List() { + const { data } = useSuspenseQuery(listQuery); + + return ( +
      + {data.list.map(item => ( + }> + + + ))} +
    + ); +} +``` + +And that's it! Suspense made our `Item` component a bit more succinct since we no longer need to check the `complete` property to determine if we can safely use `data`. + + +In most cases, `useSuspenseFragment` will not suspend when rendered as a child of a query component. In this example `useSuspenseQuery` loads the full query data before each `Item` is rendered so the `data` inside each fragment is already complete. The `Suspense` boundary in this example ensures that a loading spinner is shown if field data is removed for any given item in the list in the cache, such as when a manual cache update is performed. + + +### Using `useSuspenseFragment` with `@defer` + +`useSuspenseFragment` is helpful when combined with the [`@defer` directive](./directives#defer) to show a loading state while the fragment data is streamed to the query. Let's update our `GetItemList` query to defer loading the `ItemFragment`'s fields. + +```graphql +query GetItemList { + list { + id + ...ItemFragment @defer + } +} +``` + +Our list will now render as soon as our list returns but before the data for `ItemFragment` is loaded. + + +You **must** ensure that any key fields used to identify the object passed to the `from` option are not deferred. If they are, you risk suspending the `useSuspenseFragment` hook forever. If you need to defer loading key fields, conditionally render the component until the object passed to the `from` option is identifiable by the cache. + + ## Data masking