In Apollo Client 4, why does ProductCard declare its own fragment, and how does that fragment reach the product listing page's query?
answer
- fields live beside the renderer
- one request for the whole page
- spread plus the definition
- interpolate the child's gql document
- parents compose direct children only
basics
~10 sProductCard declares a fragment so the fields it renders live beside the code that renders them. The listing query spreads ...ProductCardFragment and interpolates its definition, so one request fetches every card's fields.
solid answer
~40 sA component declares a fragment so its data needs sit in its own file: when `ProductCard` starts showing a rating, only that file changes. `ProductCard` exports `PRODUCT_CARD_FRAGMENT`, built with `gql` from `@apollo/client`, and the listing page's query spreads `...ProductCardFragment` inside `products` and interpolates `${PRODUCT_CARD_FRAGMENT}` so the definition travels in the same document. `ProductCardFragment` in turn spreads `...PriceTagFragment` and interpolates that definition, so the page only knows about its direct child. One `useQuery` from `@apollo/client/react` then fetches every card's and price tag's fields in a single request, and the page passes each product down as a prop. Select `id` beside the spread so the cache can identify each product. With `dataMasking` off, which is the default, the page can still read the card's fields, so the coupling is a convention until masking enforces it.
code
tsx · 25 linesimport { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
import { PRODUCT_CARD_FRAGMENT, ProductCard } from "./ProductCard";
const PRODUCT_LIST = gql`
query ProductList {
products {
id
...ProductCardFragment
}
}
${PRODUCT_CARD_FRAGMENT}
`;
export function ProductListPage() {
const { data } = useQuery(PRODUCT_LIST);
return (
<ul>
{data?.products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</ul>
);
}go deeper
Recall why a component owns a fragment, and the two steps that put it in a query: the spread and the interpolated definition in the same gql document.
Walk the composition chain from PriceTag up to the page query, explain why parents spread only direct children, and name the errors a missing definition or duplicate name produces.
Point out that without dataMasking a colocated fragment is only a convention, spot the implicit dependencies it leaves, and decide when a team should turn masking on.
Set the team conventions that keep composition healthy: component-prefixed fragment names, direct-children-only spreads, id selected by the parent, and a plan for enforcing boundaries with masking.
## What a fragment does for a component A **fragment** is a named, reusable set of fields on one GraphQL type. In an Apollo Client app the common pattern is to **colocate** a fragment with the component that renders those fields: the `ProductCard` file exports both the component and a fragment on `Product` listing exactly what the card shows. The payoff is ownership. When a designer adds a star rating to the card, the engineer edits one file: the component and its fragment. Nobody has to find every query that feeds product cards and add `rating` to each of them, and nobody deletes a field from a page query without knowing which child still reads it. ## Composing ProductCard and PriceTag into one query On the product listing page the tree is `ProductListPage` → `ProductCard` → `PriceTag`. Each level declares what it renders: 1. `PriceTag` exports `PRICE_TAG_FRAGMENT`: `fragment PriceTagFragment on Product { price compareAtPrice }`. 2. `ProductCard` exports `PRODUCT_CARD_FRAGMENT`, which selects `name` and `imageUrl`, spreads `...PriceTagFragment`, and interpolates `${PRICE_TAG_FRAGMENT}` into its `gql` template so the price tag's definition comes along. 3. The page's `ProductList` query selects `products { id ...ProductCardFragment }` and interpolates `${PRODUCT_CARD_FRAGMENT}`. 4. `useQuery(PRODUCT_LIST)`, imported from `@apollo/client/react`, sends one document that contains the operation and both fragment definitions. 5. The page maps over `data.products` and passes each product to a `ProductCard`, which passes it on to its `PriceTag`. The result is **one network request** for the whole page, shaped by the union of every component's needs, instead of each card fetching its own data. ```graphql query ProductList { products { id ...ProductCardFragment } } fragment ProductCardFragment on Product { name imageUrl ...PriceTagFragment } fragment PriceTagFragment on Product { price compareAtPrice } ``` That is the document Apollo Client sends once the interpolations are resolved. ## What must be true for the composition to work - **Every spread needs its definition in the document.** `gql` interpolation is what puts it there. A query that spreads `...ProductCardFragment` without the definition, and without a fragment registry to supply it, fails: Apollo's cache reports `No fragment named ProductCardFragment`, and a GraphQL server would reject the document anyway. - **Fragment names are unique across the app.** `graphql-tag`, which backs `gql`, warns at runtime when two fragments share a name. Prefixing with the component name (`ProductCardFragment`, `PriceTagFragment`) avoids collisions. - **Parents compose only their direct children's fragments.** The page spreads `ProductCardFragment`, not `PriceTagFragment`; the card owns the decision to render a price tag. If the page spread the grandchild's fragment too, removing `PriceTag` from the card would leave a stale selection in the page. - **The parent selects the key fields.** Selecting `id` next to the spread lets the cache identify each product, which later matters for reading the product with `useFragment`. ## What fragments buy you, and what they do not | Concern | Page query lists every field | Components declare fragments | |---|---|---| | Where a new card field is added | every query that renders cards | `ProductCard.tsx` only | | Network requests for the page | one | one | | Risk of fetching fields nobody renders | grows over time | falls, since each fragment is owned | | Parent reading a child's fields | allowed | still allowed unless `dataMasking` is on | The last row is the gap. By default Apollo Client returns every field in the operation to the component that ran the query, so the page can read `product.price` even though only `PriceTag` asked for it. That creates an **implicit dependency**: if `PriceTag` later drops `compareAtPrice`, any page code that read it breaks without a type error in the file that changed. Apollo Client's `dataMasking` option closes that gap by hiding fragment fields from the parent, and child components then read their own fields with `useFragment`. ## Apollo Client 4 specifics - `gql` is exported from `@apollo/client`; hooks such as `useQuery` come from `@apollo/client/react`, since the core entry point has no React exports in version 4. - Masking is opt-in: `dataMasking` defaults to `false`, so colocated fragments on their own are a convention, not an enforced boundary. - Instead of interpolating definitions, an app can register fragments with `createFragmentRegistry` on `InMemoryCache` and spread them by name; that trades explicit imports for a registration step.
- PriceTag renders inside every ProductCard. Should the listing page's query spread ...PriceTagFragment itself as well?No. `ProductCardFragment` already spreads `...PriceTagFragment`, so the fields arrive. Apollo's guidance is that a parent composes only the fragments of its directly rendered children. If the page also spread the grandchild's fragment, it would depend on the card's internals: when the card stops rendering a price tag, the page query keeps fetching price fields nobody reads.
- Two files in the app each define a fragment called ProductFields with different fields. What goes wrong?Fragment names must be unique across the application. `graphql-tag`, which backs `gql`, warns at runtime that a fragment with that name already exists, and a document that ends up with both definitions is invalid, because GraphQL forbids two fragments with the same name in one document. Prefixing each fragment with its component name avoids this.
- Why does the listing query select id next to ...ProductCardFragment rather than leaving id to the card's fragment?The page is the level that hands each product to a child, so it should own the fields that identify the product. With `id` in the page query, the cache can identify each product whatever the card selects, and if `dataMasking` is later turned on, the object the page passes down still carries `__typename` and `id`, which is what `useFragment` in the card needs to find it.
Colocated fragments work like recipe cards that each list their own ingredients. Before shopping, the cook combines every card's list into one shopping list and makes a single trip, instead of one trip per recipe; changing a recipe means editing only its card.
saying these in an interview costs you the question
- Each component with a fragment sends its own request for its fields.
- Spreading ...ProductCardFragment is enough; Apollo finds the definition by its name.
- The page query should spread every descendant's fragment, down to PriceTag's.
- Declaring fragments already stops the page from reading the card's fields.
- Fragment names only need to be unique within one file.