Apollo Client
Apollo Client is the workhorse GraphQL client for React: a normalised cache, automatic re-fetch, optimistic updates, persisted queries, and devtools. Use it when you want fine-grained cache control, subscriptions, and predictable refetching strategies; reach for urql or relay when those specific trade-offs matter.
Apollo setup, queries, mutations, cache updates
EXAMPLE
// npm i @apollo/client graphql
import {
ApolloClient, InMemoryCache, ApolloProvider,
HttpLink, ApolloLink, gql,
useQuery, useMutation, useSubscription, useApolloClient,
} from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient as createWsClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
// 1) Compose links: auth + error + persisted queries + http/ws
const http = new HttpLink({ uri: '/api/graphql', credentials: 'same-origin' });
const ws = new GraphQLWsLink(createWsClient({ url: 'wss://app/api/graphql' }));
const auth = setContext(async (_, { headers }) => ({
headers: { ...headers, authorization: \`Bearer ${await getToken()}\` },
}));
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (networkError) console.error('[net]', networkError);
if (graphQLErrors) for (const e of graphQLErrors) console.error('[gql]', e.message, e.path);
});
const splitLink = ApolloLink.split(
({ query }) => {
const def = getMainDefinition(query);
return def.kind === 'OperationDefinition' && def.operation === 'subscription';
},
ws,
http,
);
// 2) Normalised cache with type policies (paging, ids, computed fields)
const cache = new InMemoryCache({
typePolicies: {
Order: { keyFields: ['id'] },
Query: {
fields: {
orders: {
keyArgs: ['status'], // separate cache entry per status filter
merge(existing = { data: [] }, incoming) {
return { ...incoming, data: [...existing.data, ...incoming.data] };
},
},
},
},
},
});
const client = new ApolloClient({
link: errorLink.concat(auth).concat(splitLink),
cache,
connectToDevTools: true,
defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network', errorPolicy: 'all' } },
});
// 3) Provide once at the root
export default function Root({ children }: any) {
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
// 4) Query — typed via gql template literal (or codegen — see graphql/codegen lesson)
const ORDERS = gql\`
query Orders($status: OrderStatus, $after: String) {
orders(status: $status, after: $after) {
data { id customer totalCents status createdAt }
nextCursor
}
}
\`;
function OrdersList({ status }: { status?: string }) {
const { data, loading, error, fetchMore } = useQuery(ORDERS, {
variables: { status, after: null },
});
if (loading && !data) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
const { data: rows = [], nextCursor } = data?.orders ?? {};
return (
<>
<ul>{rows.map((o: any) => <li key={o.id}>{o.customer}</li>)}</ul>
{nextCursor && (
<button onClick={() => fetchMore({ variables: { after: nextCursor } })}>
Load more
</button>
)}
</>
);
}
// 5) Mutation with optimistic update + cache write
const CANCEL_ORDER = gql\`
mutation Cancel($id: ID!) { cancelOrder(id: $id) { id status } }
\`;
function CancelButton({ id }: { id: string }) {
const [cancel] = useMutation(CANCEL_ORDER, {
optimisticResponse: { cancelOrder: { __typename: 'Order', id, status: 'cancelled' } },
update(cache, { data }) {
cache.modify({
id: cache.identify({ __typename: 'Order', id }),
fields: { status: () => data!.cancelOrder.status },
});
},
});
return <button onClick={() => cancel({ variables: { id } })}>Cancel</button>;
}
// 6) Subscription
const TICKER = gql\`subscription { ticker { id price } }\`;
function PriceTicker() {
const { data } = useSubscription(TICKER);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
async function getToken() { return 'token-here'; }
Why it matters
Set defaultOptions.watchQuery.fetchPolicy to "cache-and-network" so screens render instantly from the cache, then refresh in the background. Combined with type policies that paginate properly, the UI feels as fast as a fully cached app and stays consistent with the server without any explicit refetch glue.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
const client = new ApolloClient({ uri: '/graphql', cache: new InMemoryCache() });
const { data } = await client.query({ query: gql\`{ me { id name } }\` });
Try it Yourself »
Exercise
Apollo Client cache class is…
new ApolloClient({ cache: new
() })
PascalCase; 13 chars.
Discussion
Loading…