Apollo Client Local and Remote State Management
Apollo Client functions as a comprehensive state management solution for JavaScript applications, unifying remote data from GraphQL APIs and local client state into a single cohesive architecture. By leveraging an in-memory normalized cache, reactive variables, and client-side field policies, Apollo Client eliminates the need for separate state management libraries like Redux or Zustand. This article explores the mechanisms Apollo Client uses to fetch, cache, and synchronize remote data while seamlessly handling local-only application state.
Remote State Management
Remote state refers to data stored on a server that the client application fetches, displays, and modifies. Apollo Client handles this lifecycle automatically through its caching layer and declarative query mechanism.
Normalized In-Memory Cache
At the core of Apollo Client’s remote state management is the
InMemoryCache. When a GraphQL query returns data, the cache
normalizes it through three main steps:
- Splitting Objects: The cache identifies individual
objects within the nested response using a unique identifier (by
default,
__typenamecombined with anidor_idfield). - Flattening the Tree: Objects are stored as a flat lookup table of entities rather than deeply nested query results.
- Deduplicating Data: If multiple queries request the same entity, it is stored only once in the cache.
When a mutation updates an existing entity containing the matching identifier, Apollo Client updates the normalized record automatically, triggering an immediate re-render across all UI components listening to that data.
Fetch Policies
Apollo Client uses fetch policies to control how queries interact with the local cache and the network:
cache-first(default): Returns cached data if available; otherwise, sends a network request.cache-and-network: Returns cached data immediately for fast rendering, then executes a network request to update the cache and UI with fresh server data.network-only: Bypasses the cache to fetch directly from the network, but saves the result to the cache.no-cache: Fetches directly from the network without storing the response in the cache.cache-only: Resolves strictly against local cache data without making a network request.
Local State Management
Local state refers to client-only data, such as UI theme preferences, form draft states, or modal visibility flags. Apollo Client provides two primary tools to manage local state: Reactive Variables and Field Policies.
Reactive Variables
Reactive variables (makeVar) allow developers to store
local state outside the Apollo Client cache while still triggering UI
updates. They are lightweight, require no GraphQL schema definitions,
and can be read or modified anywhere in the application.
import { makeVar } from '@apollo/client';
// Create a reactive variable
export const isDarkModeVar = makeVar(false);
// Read the variable
const isDark = isDarkModeVar();
// Update the variable
isDarkModeVar(true);When a component reads a reactive variable using the
useReactiveVar hook, it automatically re-renders whenever
the variable value changes.
Field Policies and the
@client Directive
For applications that prefer managing local state entirely through
GraphQL operations, Apollo Client supports Type Policies and the
@client directive.
Developers define custom read and merge
functions inside the InMemoryCache configuration:
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
isDarkMode: {
read() {
return isDarkModeVar();
},
},
},
},
},
});Clients can then request this field alongside remote fields in a
standard GraphQL query by applying the @client directive,
signaling to Apollo Client that the field should be resolved locally
rather than sent to the backend server.
The Unified Graph Architecture
The primary advantage of Apollo Client’s state model is the unified data graph. Developers can execute a single query that retrieves both remote server data and local UI state simultaneously:
query GetUserProfile {
user(id: "123") {
name
email
}
isDarkMode @client
cartItemCount @client
}Apollo Client strips out the @client fields before
sending the network request, retrieves the remote data, resolves the
local fields via defined policies or reactive variables, and returns a
single merged result to the component. This design minimizes
boilerplate, consolidates debugging through Apollo DevTools, and
provides a single, consistent API for all application state.