iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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 () })

Discussion

Loading…