Apollo Client patterns and data loading strategies.
Quick Reference
Use Apollo Client for:
GraphQL queries and mutations
Client-side caching
Optimistic updates
Key patterns:
Fetch with useQuery, not useEffect + fetch. A page's query is the default; a component runs its own query only in the justified cases
Components that render data a parent's query loads declare a Component_prop fragment, take a FragmentType prop, and read it with useFragment
useMutation for data updates
Apollo cache shared across component instances
Show a failure state for an Apollo error; don't report it. The shell's Apollo client reports it once (Error Handling)
Apollo Client Basics
useQuery for Fetching
UserList owns the query. It selects id on each user and spreads the fragment of the component it renders, UserCard. Fragments by Default shows that fragment and how UserCard reads it.
useMutation for Updates
Fragments by Default, an Own Query When Justified
The page's query makes one request. Every component below it that renders that data declares the fields it reads in its own fragment and reads them with useFragment, which reads from the cache and never makes a request. When a component may run its own query instead, see When a component runs its own query.Each child owns a fragment in a fragments.graphql next to it, named Component_prop:
The child takes a FragmentType prop and opens it with useFragment. Always pass fragmentName: it keeps working when someone later adds a ...Child spread to the fragment.
The route's one query returns the data for every child, and masking hands each component only what it selected. The fragment rules (one per component, never shared, spread by the parent) live on GraphQL Fragments.To test a component like UserCard on its own, see Testing a Fragment-Owning Component on Its Own.Apollo also deduplicates identical in-flight queries, so two useQuery calls with the same variables make one request. That is a safety net for edge cases, not the design: don't build components on it.
One Operation per Route
Prefer a single operation that selects everything the route needs. A second query that waits for the first (a waterfall) or several independent queries in one component are the patterns to avoid:
When two pieces of data are independent and have separate lifetimes (different polling, different loading states), put each in its own query. Queries mounted together run in parallel automatically.
Skip Queries Conditionally
Polling
Polling belongs on the query that owns the data. IncidentCard declares an IncidentCard_incident fragment, and the query spreads it, so the polled response reaches the cards through the cache:
Optimistic Updates
Cache Updates
Error Handling
Show the user a failure state and stop. Don't call logger.errorAndReport for an Apollo error: the shell's Apollo client reports a network or transport failure once, after any retries, and the backend has already reported an error it returned in the response. See Who reports an error. (useQuery has no onError option in Apollo 4.)
Lazy Queries
Fetching in Components vs Hooks
In Components
A component that renders data its parent already loads takes a FragmentType prop and reads it with useFragment; it doesn't take an id and fetch for itself. The cases where a component owns a query are in GraphQL Fragments.
The component that owns the query decides the loading state, and renders the card only once it has data.
In Hooks (Reusable Logic)
A custom hook in the component that owns the query is for logic that adds something, such as mapping the data or guarding access. A hook that only calls useQuery with a fixed Document adds a file and no behavior. See GraphQL Operations.
Event Listener Deduplication
Share global event listeners across component instances: