Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for useSuspenseFragment #12356

Merged
merged 5 commits into from
Feb 11, 2025
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions docs/source/data/fragments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed this typo when reading through so I went ahead and fixed this.

});

return <li>{complete ? data.text : "incomplete"}</li>;
Expand All @@ -573,7 +573,7 @@ function Item(props) {
const { complete, data } = useFragment({
fragment: ITEM_FRAGMENT,
fragmentName: "ItemFragment",
from: item
from: props.item
});

return <li>{complete ? data.text : "incomplete"}</li>;
Expand All @@ -589,6 +589,71 @@ function Item(props) {

See the [API reference](../api/react/hooks#usefragment) for more details on the supported options.

<MinVersion version="3.13.0">
## `useSuspenseFragment`
</MinVersion>

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 fully 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 <li>{data.text}</li>;
}
```

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 (
<ol>
{data.list.map(item => (
<Suspense fallback={<Spinner />}>
<Item key={item.id} item={item}/>
</Suspense>
))}
</ol>
);
}
```

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 use safely use `data`.

<Note>
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.
</Note>

### 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.

<Caution>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we are trying to use <Caution> sparingly, but this one seemed like a good use of it. If you don't abide by this rule then you will have a forever-suspending component which might frustrate you. I'm welcome to other ideas if this still doesn't pass the bar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, definitely something worth cautioning!

You **must** ensure that any key fields used to identify the object passed to the `from` option are not deferred, otherwise you risk suspending the `useSuspenseFragment` hook forever. If you need to defer loading key fields for any reason, the component itself will need to be conditionally rendered until the object passed to the `from` option is identifiable by the cache.
</Caution>

<MinVersion version="3.12.0">
## Data masking
</MinVersion>
Expand Down