Guide
Diagnosing Apollo Client Cache Inconsistency After Mutations
A step‑by‑step diagnostic guide for Apollo Client cache inconsistencies after mutations, with checks, fixes, and escalation paths.
Published by Tasadduq Burney
04 Jul 2025, 00:43 UTC
4 min32.3K views0

Recognizable condition
After a mutation is sent from the UI, the screen still shows the old data or a perpetual loading spinner, even though the network request appears to have succeeded.
Cause / diagnostic table
| Symptom | Likely cause |
|---|---|
| Mutation returns 200 but UI unchanged | Cache not updated after mutation |
| UI shows loading indicator that never resolves | fetchPolicy set to 'cache-only' or error swallowed |
| Old data flashes then disappears | Missing or incorrect optimisticResponse |
| Related fields stay stale | Type policy key fields mis‑configured |
| Network error but UI shows success | errorPolicy ignores errors |
Ordered checks
- Verify network payload
- Open browser DevTools → Network tab.
- Find the mutation request (POST to GraphQL endpoint).
- Check that status is 200 and the response JSON contains the expected data fields (e.g.,
{ updateItem { id name } }). - If the request failed or returned an error, note the error message.
- Inspect the Apollo Client cache
- Ensure Apollo Client DevTools extension is installed.
- In the DevTools panel, select the Cache tab.
- Search for the typename and id of the object you expect to change (e.g.,
Item:"123"). - Confirm that the fields returned by the mutation are present and have correct
__typenameandid. - If the cache still shows the old values, the mutation did not update the cache.
- Check optimistic response handling
- Locate the mutation call in your code.
- Verify that an
optimisticResponseobject is supplied (if you rely on optimistic UI). - If omitted, the UI will not reflect changes until the server response arrives.
- Review fetchPolicy and errorPolicy
- Find the
useMutationhook ormutatecall. - Ensure
fetchPolicyis not set to'cache-only'(which would prevent the mutation result from hitting the network). - Check
errorPolicy: if set to'ignore', errors are swallowed and the UI may appear stuck. - Typical safe values:
fetchPolicy: 'network-only'or'cache-and-network';errorPolicy: 'all'or'none'.
- Find the
- Examine type policies and key fields
- When initializing
InMemoryCache, verify that each type has a proper key fields definition (usuallyidor customkeyFields). - Incorrect key fields cause Apollo to treat updated objects as new entities, leaving the old cache entry untouched.
- Example snippet:
const cache = new InMemoryCache({ typePolicies: { Item: { keyFields: ['id'] }, // add other types as needed } });
- When initializing
Fixes tied to findings
- Missing cache update
- Add an
updatefunction to the mutation: const [updateItem] = useMutation(UPDATE_ITEM, { update: (cache, { data: { updateItem } }) => { cache.modify({ id: cache.identify(updateItem), fields: { name: () => updateItem.name, }, }); }, });- Alternatively, use
refetchQueriesto refetch a query that reads the updated field.
- Add an
- Optimistic UI missing
- Provide an
optimisticResponsethat matches the shape of the mutation result: const [updateItem] = useMutation(UPDATE_ITEM, { optimisticResponse: { updateItem: { __typename: 'Item', id: variables.id, name: variables.newName, }, }, });
- Provide an
- fetchPolicy blocking network
- Change the fetchPolicy in the mutation options:
const [updateItem] = useMutation(UPDATE_ITEM, { fetchPolicy: 'network-only', });
- errorPolicy swallowing errors
- Set errorPolicy to
'all'to surface errors, or handle them in the mutation’sonErrorcallback. const [updateItem] = useMutation(UPDATE_ITEM, { errorPolicy: 'all', onError: (err) => console.error('Mutation failed', err), });
- Set errorPolicy to
- Type policy mis‑configuration
- Define correct key fields for each type, as shown in the cache initialization example above.
- If you use composite keys, ensure they uniquely identify the object across the cache.
Escalation criteria
- Stale data persists after applying the relevant fix above.
- Multiple unrelated mutations exhibit the same cache inconsistency, suggesting broader cache corruption.
- You need custom logic that cannot be expressed with standard
updateorrefetchQueries(e.g., conditional updates based on other cache entries). - Consider enabling
cacheRedirectsinInMemoryCacheto map fields to existing cache entries. - Or use
apollo-link-stateto manage local state alongside remote data. - If the issue remains unresolved, prepare a minimal reproduction (e.g., using
MockedProvider) and open a GitHub issue on the Apollo Client repository, including:- Apollo Client version (e.g., 3.9.0).
- Relevant code snippets (mutation call, cache config).
- Network payload and cache snapshot screenshots.
Verification
- After applying a fix, re‑run the mutation and confirm the network response contains the expected data (status 200).
- Open Apollo Client DevTools → Cache tab and verify that the updated fields now reflect the new values.
- In a unit test, render the component with
MockedProvider, mock the mutation to return the new data, and assert that the UI displays the updated information after the mutation resolves.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.