Axon 911 Frontend Docs
    Loading navigation…
U
User
Switch to light mode
  1. Working With Data
  2. Graphql Loading And Freshness

GraphQL Freshness and Loading States

How fresh data reaches the cache, how each component shows a pending query, and where one component's choice reaches another

Several components often read the same data through the one Apollo cache. Two questions come up for each query, and they have different owners:
QuestionOwnerRule
How does fresh data get into the cache? (fetching and freshness)Shared. Every component that reads the data benefitsOne recipe per kind of data. Deviate only for a data-freshness need, never for a display need
What does this component show while a request is pending?The componentFree to differ from its neighbours, as long as it never blanks data it already has
The first is about the cache, so one component's choice reaches all the others. The second is about one component's own render, so it reaches nobody. Keep them apart: a fetchPolicy picked to make a spinner behave is a freshness decision made for a display reason, and it changes what every other reader of that data gets. This page describes the client as it is configured today. createApolloClient sets no defaultOptions, so every option below is Apollo Client 4's own default unless a call passes one. If a shell later pins defaults, these rules still hold; only the per-call options you need to write change.
Fetching and freshness
Apollo 4's fetchPolicy defaults to cache-first: a query that finds all its data in the cache makes no request. That is right for most data. A live screen also needs a way to catch up, and there are two, which work together:
RecipeWhat it doesUse it when
A subscriptionWrites every change to the cache while the component is mountedThe data changes while the user watches. See Pub/Sub
cache-and-network with nextFetchPolicy: "cache-first"Shows what the cache has, fetches once on mount to catch up on anything missed, then goes back to cache-first so later cache writes do not trigger more requestsThe user can arrive after the data changed, and a subscription alone would not have told them
Neither (plain cache-first)Reads the cacheThe data is client-only, or another mounted component already subscribes to it
A live screen, such as a chatroom list, uses both: the query catches up on mount and the subscription keeps it current.
  • nextFetchPolicy applies after the first request, and Apollo resets it to the original policy when the variables change, so a new filter catches up once too.
  • Set fetchPolicy on the query that needs the catch-up, not shell-wide. A global cache-and-network makes every query in every remote refetch on mount.
  • Avoid network-only. It fetches on every mount and gives the same result as cache-and-network with a worse loading state. Use cache-and-network with nextFetchPolicy.
  • no-cache only for a root whose data no fragment reads. It writes nothing, so a child's useFragment finds nothing (see Why is useFragment incomplete?).
  • Do not poll to stay fresh when a subscription exists. A poll is a network-only fetch, and its result is written to the cache for every reader of that data.
Showing a pending query
useQuery returns data, previousData, dataState, networkStatus and loading. Apollo 4 sets notifyOnNetworkStatusChange to true unless a call says otherwise, so loading is true for every request in flight: the first load, a refetch(), a poll, fetchMore, and a change of variables. data stays put on a refetch or poll, and on a variable change it becomes the new variables' cached result, or undefined while there is none, with the old result in previousData.
The rule: a skeleton only when there is no data
Never write if (loading) return <Skeleton />. It blanks a screen that is only refreshing, and it did exactly that to the Dispatch chatroom list in March 2026. Show a skeleton when the component has nothing to show. Pick one of three recipes for what happens the rest of the time. A. Keep showing the old data. The default for lists. Rows stay put and new data replaces them when it arrives.
B. Keep the old data and mark it stale. Show the rows, dimmed or with a spinner, while a refresh is in flight.
notifyOnNetworkStatusChange: true is already Apollo 4's default. Keep it in the call anyway, so this component does not break if a shell pins the default to false. C. Skeleton when the old data is the wrong data. After a filter or tab change, the previous rows answer a different question, so do not show previousData. Show a skeleton until the new variables have data, and keep the data through a refetch or poll.
To keep loading itself false during a refetch or poll in one component, pass notifyOnNetworkStatusChange: false on that call. It changes what that component re-renders for, and nothing else. A hook that wraps a query and adds mapping or derived state should return flags with their own names (isFirstLoad, isRefreshing), never a bare loading that means something different from Apollo's.
What does not reach other components
One component's display choice does not affect another component that reads the same data:
  • Each useQuery call has its own ObservableQuery. previousData, networkStatus, notifyOnNetworkStatusChange and fetchPolicy belong to that one call.
  • A cache write updates other readers' data, not their pending state. They re-read the cache and re-render with the new data, with no request.
  • refetch() affects only the caller. Others see the result of its cache write.
  • Request deduplication shares the network request, not the state. If two components ask for the same query and variables while one request is in flight, Apollo sends one request. Each keeps its own networkStatus.
Where one component's choice does reach another
These do reach other components, so treat them as shared decisions:
Action in component AEffect on component B that reads the same dataWhat to do
refetch(), a cache-and-network fetch or a pollB's data updates in place; its pending state does not changeExpected. Freshness is shared
A mutation with refetchQueries: [Doc], or client.refetchQueries({ include: [Doc] })Every active query on Doc, for all variables, refetches and goes into its refetch state. A no-cache query refetches tooReturn the changed entities and update the cache first. If a refetch is still needed, narrow it (see Mutation order)
client.resetStore()Every query loses its data and refetchesOnly when the user's identity changes
A writes a smaller selection of a list, or of an object with no identityLists are replaced on write, because no merge policy exists. If B reads more fields than A wrote, B goes to the network on its ownGive both roots the same arguments and identity. A list shared between roots needs keyArgs and merge in createPreparedInMemoryCache, with a test
fetchMore with a merge policyThe merged list is written to the shared field, so B sees the extra pagesPage a list from one root
no-cacheNothing is written, so B is not affected, but A's own fragments read nothingOnly for data no fragment reads
Related documentation
  • GraphQL Cache - Identity, updating the cache after mutations and subscriptions, and the order to reach for
  • Backend Responses - What a mutation or subscription should return so the frontend needs none of the workarounds above
  • Pub/Sub - Subscriptions and live updates
  • GraphQL Operations - Writing queries, mutations, and subscriptions
Previous
Working with data / GraphQL Cache
Next
Working with data / GraphQL Backend Responses
On this page
Fetching and freshness
Showing a pending query
The rule: a skeleton only when there is no data
What does not reach other components
Where one component's choice does reach another
Related documentation