In Apollo Client 4, how does useFragment let ProductCard read its fields from the cache, and when does it return complete: false?
answer
- a live view, not a fetch
- from names the cached entity
- type name plus key fields
- fragmentName picks one definition
- missing fields mean partial data
basics
~20 suseFragment is a live, read-only binding to one cached entity: from identifies the product, the hook reads the fragment's fields and re-renders when they change. It never fetches; complete is false while any selected field is missing.
solid answer
~50 s`useFragment`, imported from `@apollo/client/react`, takes the `fragment` document, a `fragmentName` when that document holds several definitions, and `from`, which identifies the entity: an object with `__typename` and its key fields, a `{ __ref }` reference or a cache ID string. The hook computes the cache ID, watches that fragment on that entity and returns `data`, `complete`, `dataState` (`"complete"` or `"partial"`) and, when fields are missing, a `missing` tree. It never sends a request: a query such as the listing page's `useQuery` must have written the fields first. `complete` is false when the product is not in the cache yet, when some selected field was never fetched, or when `from` cannot be identified because the parent did not select the key fields, which also logs a development warning. On its own, the hook re-renders the card only when that fragment's data changes.
code
tsx · 23 linesimport { useFragment } from "@apollo/client/react";
import { PRODUCT_CARD_FRAGMENT } from "./fragments";
import { PriceTag } from "./PriceTag";
type Props = { product: { __typename: "Product"; id: string } };
export function ProductCard({ product }: Props) {
const { data, complete } = useFragment({
fragment: PRODUCT_CARD_FRAGMENT,
fragmentName: "ProductCardFragment",
from: product,
});
if (!complete) return null;
return (
<article>
<img src={data.imageUrl} alt="" />
<h3>{data.name}</h3>
<PriceTag product={product} />
</article>
);
}go deeper
Recall that useFragment reads from the cache and never fetches, and that from needs the entity's __typename and id.
Explain the options and the result fields, and list the causes of complete: false, including a parent query that forgot the key fields and the warning it logs.
Use useFragment with @nonreactive to stop list-wide re-renders, and treat an incomplete fragment as a signal of a parent or cache configuration bug.
Weigh passing props down against per-component cache subscriptions: narrower re-renders and clearer ownership against more subscriptions and the discipline of always selecting key fields.
## What useFragment is `useFragment` is an Apollo Client React hook, imported from `@apollo/client/react` in version 4, that gives a component a **live, read-only view of one cached entity** through a fragment. It has been stable since Apollo Client 3.8. Two properties define it: - It **never makes a network request.** Some operation, usually the parent's `useQuery`, must already have written the entity and its fields into `InMemoryCache`. - It **subscribes to that entity's fragment data.** When a mutation, a refetch or a direct cache write changes `Product:42`'s price, the card that watches `Product:42` re-renders with the new value; the hook itself triggers a re-render only when its own fragment's data changes, although a re-rendering parent still re-renders its children as usual. ## The options | Option | What it carries | Notes | |---|---|---| | `fragment` | the `gql` document with the fragment | required | | `fragmentName` | which definition to read | required when the document holds more than one fragment | | `from` | the entity to read | object with `__typename` and key fields, a `{ __ref }` reference, a cache ID string, or `null` | | `variables` | values for variables the fragment's fields use | optional | | `optimistic` | read the optimistic layer or not | defaults to `true` | | `client` | a specific `ApolloClient` instance | defaults to the one from context | `fragmentName` matters more often than people expect. `PRODUCT_CARD_FRAGMENT` interpolates `PRICE_TAG_FRAGMENT`, so its document contains two definitions, and the card must say `fragmentName: "ProductCardFragment"` to pick its own. ## What it returns - `data`: the fragment's fields for that entity; partial when something is missing. - `complete`: `true` when every selected field was found. - `dataState`: `"complete"` or `"partial"`, matching `complete` and used by TypeScript to narrow `data`. - `missing`: when incomplete, a tree of the missing-field messages, keyed by path. ## When complete is false 1. **The entity is not in the cache yet.** `Product:42` has never been written, so every field is missing. 2. **A field was never fetched.** The fragment selects `rating`, but the query that wrote `Product:42` spread an older version of the fragment or selected the fields by hand without it. 3. **The entity cannot be identified.** The object passed as `from` lacks the key fields, because the parent query did not select `id`. `cache.identify` returns nothing, and in development Apollo warns: `Could not identify object passed to from for 'ProductCardFragment' fragment, either because the object is non-normalized or the key fields are missing.` 4. **`from` is `null`.** For a single entity, version 4 returns `data: {}` with `complete: false` for backwards compatibility; `useSuspenseFragment` returns `data: null` instead. A component should check `complete` (or `dataState`) before trusting the data, because an incomplete result usually means a configuration problem in a parent, not a slow network. ## Where from comes from The usual pattern is that the parent passes each product object down as a prop and the child hands that object to `from`. The hook calls `cache.identify` on it, so only `__typename` and the key fields matter; any other fields on the prop are ignored. Passing `{ __typename: "Product", id: props.id }` works just as well. Because the hook tracks the computed cache ID rather than the object, a new prop object for the same product reuses the existing subscription instead of starting a new one. ## Re-rendering: what the card buys the list With a plain `useQuery`, the listing page re-renders whenever any field in its result changes, and every card re-renders with it. With `useFragment`, each card watches its own entity. Adding `@nonreactive` to the `...ProductCardFragment` spread in the page query goes further: changes inside that subtree no longer re-render the page, so a price change on one product re-renders one card. The page then only needs to pass each card its key fields. ## Arrays and Suspense - Since **Apollo Client 4.1**, `from` accepts an array. `data` is then an array aligned by index, `complete` is true only when every item is complete, `null` items come back as `null` and count as complete, and an empty array is complete. - **`useSuspenseFragment`** takes the same options but suspends while the data is incomplete and returns only `data`, so the component needs a React Suspense boundary instead of a `complete` check. Under a parent query that has already loaded, it rarely suspends; it matters with `@defer`, where the key fields passed to `from` must not themselves be deferred.
- Changing one product's price re-renders the whole listing page and all its cards. How do useFragment and @nonreactive fix that in Apollo Client?Have each `ProductCard` read its fields with `useFragment` from the product's key fields, and mark the page's spread `...ProductCardFragment @nonreactive`. Changes inside that subtree then no longer re-render the component that ran the query, while each card's `useFragment` still watches its own entity. A price change re-renders one card instead of the page and every card.
- What changes if ProductCard uses useSuspenseFragment instead of useFragment?`useSuspenseFragment` takes the same options but suspends while the fragment data is incomplete and returns only `data`, so there is no `complete` check and a React Suspense boundary shows the fallback. Under a parent query that already loaded, it seldom suspends. With `@defer`, keep the fields that identify the product out of the deferred fragment, or the hook can suspend forever.
- A ProductGrid receives an array of product references. Can one useFragment call watch all of them in Apollo Client 4?Yes, since 4.1 `from` accepts an array. `data` comes back as an array in the same order, `complete` is true only when every item is complete, `null` entries come back as `null` and count as complete, and an empty array is complete. Separate `useFragment` calls in each child still re-render more narrowly.
saying these in an interview costs you the question
- useFragment fetches the fragment from the server when the cache lacks it.
- useFragment needs the whole product object, not just __typename and id.
- complete: false from useFragment means the network request failed.
- useFragment accepts a fetchPolicy option, just like useQuery.
- When a document holds several fragments, useFragment reads the first one.
- In Apollo Client 4, useFragment is imported from @apollo/client.