Ultimate Apollo Client Cheat Sheet for React Developers

Introduction

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. It allows you to fetch, cache, and modify application data, all while automatically updating your UI. Apollo Client helps you structure code in a predictable and declarative way that’s consistent with modern React practices.

Core Concepts

Key Components of Apollo Client

  • Apollo Client: The core client instance that manages data and connects to GraphQL API
  • Apollo Provider: The React component that makes Apollo Client available in your React component tree
  • Apollo Cache: The normalized data store that manages query and mutation results (InMemoryCache)
  • Apollo Link: The network layer that customizes request handling from client to GraphQL server
  • React Hooks: Functional components for interacting with Apollo Client (useQuery, useMutation, etc.)

Setting Up Apollo Client

Basic Setup

import { ApolloClient, InMemoryCache, ApolloProvider, HttpLink } from '@apollo/client';

// Create an HTTP link
const httpLink = new HttpLink({
  uri: 'https://your-graphql-endpoint.com',
});

// Create the Apollo Client instance
const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
});

// Wrap your app with ApolloProvider
function App() {
  return (
    <ApolloProvider client={client}>
      <YourApp />
    </ApolloProvider>
  );
}

Advanced Setup with Authentication

import { ApolloClient, InMemoryCache, ApolloProvider, HttpLink, from } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

// HTTP connection to the API
const httpLink = new HttpLink({ uri: 'https://your-graphql-endpoint.com' });

// Auth link for adding token to requests
const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token');
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  };
});

// Combine the auth and http links
const client = new ApolloClient({
  link: from([authLink, httpLink]),
  cache: new InMemoryCache()
});

Querying Data

Basic Query with useQuery Hook

import { useQuery, gql } from '@apollo/client';

const GET_DOGS = gql`
  query GetDogs {
    dogs {
      id
      breed
      name
    }
  }
`;

function Dogs() {
  const { loading, error, data } = useQuery(GET_DOGS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.dogs.map(dog => (
        <li key={dog.id}>{dog.name} ({dog.breed})</li>
      ))}
    </ul>
  );
}

Query with Variables

const GET_DOG = gql`
  query GetDog($id: ID!) {
    dog(id: $id) {
      id
      name
      breed
    }
  }
`;

function DogProfile({ dogId }) {
  const { loading, error, data } = useQuery(GET_DOG, {
    variables: { id: dogId },
  });

  // Component logic...
}

Query Options

OptionDescription
variablesAn object containing all variables to be sent with the query
fetchPolicyDetermines how data is retrieved from the cache (e.g., ‘cache-first’, ‘network-only’)
errorPolicyDetermines how errors are handled (e.g., ‘none’, ‘all’, ‘ignore’)
skipBoolean to skip this query (useful for conditional queries)
pollIntervalNumber in ms for polling interval (refetch at regular intervals)
notifyOnNetworkStatusChangeBoolean to trigger rerenders on network status changes
contextObject passed to Apollo Links in the context property
onCompletedCallback executed when query completes successfully
onErrorCallback executed when the query errors

Fetch Policies

PolicyDescription
cache-firstDefault. Checks cache first, then network if cache miss
cache-onlyOnly checks cache, errors if data not found
network-onlyAlways queries the server, never uses cache
cache-and-networkReturns cached data then updates from network
no-cacheLike network-only but doesn’t save to cache
standbyInitial request behaves like cache-first, but doesn’t update on cache changes

Mutations

Basic Mutation with useMutation Hook

import { useMutation, gql } from '@apollo/client';

const ADD_DOG = gql`
  mutation AddDog($name: String!, $breed: String!) {
    addDog(name: $name, breed: $breed) {
      id
      name
      breed
    }
  }
`;

function AddDogForm() {
  const [name, setName] = useState('');
  const [breed, setBreed] = useState('');
  const [addDog, { data, loading, error }] = useMutation(ADD_DOG);

  const handleSubmit = (e) => {
    e.preventDefault();
    addDog({ variables: { name, breed } });
  };

  // Form JSX and logic...
}

Updating the Cache After Mutation

const [addDog] = useMutation(ADD_DOG, {
  update(cache, { data: { addDog } }) {
    // Read existing dogs from the cache
    const { dogs } = cache.readQuery({ query: GET_DOGS });
    
    // Update the cache with the new dog
    cache.writeQuery({
      query: GET_DOGS,
      data: { dogs: [...dogs, addDog] },
    });
  }
});

Optimistic Updates

const [addDog] = useMutation(ADD_DOG, {
  optimisticResponse: {
    __typename: 'Mutation',
    addDog: {
      __typename: 'Dog',
      id: 'temp-id',
      name: formValues.name,
      breed: formValues.breed,
    }
  },
  update(cache, { data: { addDog } }) {
    // Update cache logic...
  }
});

Mutation Options

OptionDescription
variablesVariables for the mutation
optimisticResponseThe expected result used for optimistic UI
refetchQueriesQueries to refetch after the mutation
updateFunction used to update the cache after the mutation
onCompletedCallback for when the mutation completes
onErrorCallback for when the mutation errors
contextContext to be passed to Apollo Links
fetchPolicyHow to interact with the cache

Caching Strategies

Cache Configuration

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      // Unique identifier for User type
      keyFields: ['id'],
      fields: {
        // Custom field policy for friends
        friends: {
          // Merge strategy for this field
          merge(existing = [], incoming) {
            return [...existing, ...incoming];
          }
        }
      }
    }
  }
});

Manual Cache Operations

OperationDescriptionExample
readQueryRead data from cache without API callconst { dogs } = client.readQuery({ query: GET_DOGS });
writeQueryWrite data to cache manuallyclient.writeQuery({ query: GET_DOGS, data: { dogs: [...] } });
readFragmentRead a fragment from any object in cacheconst dog = client.readFragment({ id: 'Dog:1', fragment: DOG_FRAGMENT });
writeFragmentWrite a fragment to any object in cacheclient.writeFragment({ id: 'Dog:1', fragment: DOG_FRAGMENT, data: dogData });
evictRemove specific data from cacheclient.cache.evict({ id: 'Dog:1' });
resetReset entire cacheclient.resetStore();

Field Policies Types

PolicyPurpose
keyFieldsConfigure how to identify entities (instead of the default id or _id)
mergeDefine custom logic for merging incoming data with existing cached data
readDefine custom logic for reading a field from the cache

Error Handling

Error Policies

PolicyDescription
noneTreat GraphQL errors as runtime errors (default)
ignoreIgnore GraphQL errors and still display data
allReturn both data and errors if they exist

Common Error Patterns

// Component with error fallback
function DogList() {
  const { loading, error, data } = useQuery(GET_DOGS);
  
  if (loading) return <LoadingSpinner />;
  
  if (error) {
    // Network error
    if (error.networkError) {
      return <NetworkErrorMessage error={error.networkError} />;
    }
    
    // GraphQL errors
    if (error.graphQLErrors) {
      return (
        <div>
          {error.graphQLErrors.map(({ message }, i) => (
            <ErrorBanner key={i} message={message} />
          ))}
        </div>
      );
    }
    
    return <GenericError message={error.message} />;
  }
  
  return <DogListDisplay dogs={data.dogs} />;
}

Local State Management

Local-Only Fields

// Query with both server and local fields
const GET_DOG_WITH_LOCAL_STATE = gql`
  query GetDogWithLocalState($id: ID!) {
    dog(id: $id) {
      id
      name
      breed
      # Local-only field (client-side)
      isSelected @client
    }
  }
`;

TypePolicies for Local Fields

const cache = new InMemoryCache({
  typePolicies: {
    Dog: {
      fields: {
        isSelected: {
          // Default value for isSelected field
          read(_, { variables }) {
            // Read from localStorage or return default
            return localStorage.getItem(`selected_${variables.id}`) === 'true' || false;
          }
        }
      }
    }
  }
});

Reactive Variables

import { makeVar, useReactiveVar } from '@apollo/client';

// Create a reactive variable
export const cartItemsVar = makeVar([]);

// In a component
function Cart() {
  // Subscribe to changes
  const cartItems = useReactiveVar(cartItemsVar);
  
  function addToCart(item) {
    // Update the variable - triggers re-renders anywhere useReactiveVar is used
    cartItemsVar([...cartItemsVar(), item]);
  }
  
  // Component logic...
}

Performance Optimization

Techniques and Best Practices

TechniqueDescriptionImplementation
Query DeduplicationBatches identical queries made in the same tickEnabled by default
Query SplittingSplit large queries into smaller onesCreate multiple focused queries
PaginationFetch data in chunksUse fetchMore with existing query
Fragment ColocationDefine fragments close to componentsCreate fragments in component files
PrefetchingLoad data before it’s neededclient.query() on hover or route change
Selective PollingUpdate specific data at intervalsUse pollInterval on useQuery
Skip QueriesPrevent queries from runningUse skip: true option

Example: Pagination with fetchMore

function DogList() {
  const { loading, error, data, fetchMore } = useQuery(GET_DOGS, {
    variables: { offset: 0, limit: 10 },
  });

  const loadMore = () => {
    fetchMore({
      variables: {
        offset: data.dogs.length,
        limit: 10,
      },
      updateQuery: (prev, { fetchMoreResult }) => {
        if (!fetchMoreResult) return prev;
        return {
          dogs: [...prev.dogs, ...fetchMoreResult.dogs],
        };
      },
    });
  };

  // Component logic with loadMore button...
}

Advanced Techniques

Using Apollo Link

import { ApolloClient, InMemoryCache, ApolloLink, HttpLink, from } from '@apollo/client';
import { onError } from '@apollo/client/link/error';
import { RetryLink } from '@apollo/client/link/retry';

// Log operations
const loggerLink = new ApolloLink((operation, forward) => {
  console.log(`Operation: ${operation.operationName}`);
  return forward(operation);
});

// Handle errors
const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors)
    graphQLErrors.forEach(({ message, locations, path }) =>
      console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
    );
  if (networkError) console.log(`[Network error]: ${networkError}`);
});

// Retry failed requests
const retryLink = new RetryLink({
  delay: {
    initial: 300,
    max: 3000,
    jitter: true
  },
  attempts: {
    max: 5,
    retryIf: (error, _operation) => !!error
  }
});

// HTTP link
const httpLink = new HttpLink({ uri: 'https://your-graphql-endpoint.com' });

// Combine links
const client = new ApolloClient({
  link: from([loggerLink, errorLink, retryLink, httpLink]),
  cache: new InMemoryCache()
});

Common Apollo Links

LinkPurpose
HttpLinkConnects to a GraphQL server over HTTP
WebSocketLinkConnects to a GraphQL server over WebSocket for subscriptions
RetryLinkRetries failed operations
ErrorLinkHandles and modifies errors
ApolloLink.fromCombines multiple links
ApolloLink.splitDirects operations through different links based on criteria
BatchHttpLinkBatches operations sent over HTTP
ContextLinkSets operation context

Using Fragments for Component Reusability

const DOG_FRAGMENT = gql`
  fragment DogDetails on Dog {
    id
    name
    breed
    displayImage
    description
  }
`;

const GET_DOG = gql`
  query GetDog($id: ID!) {
    dog(id: $id) {
      ...DogDetails
    }
  }
  ${DOG_FRAGMENT}
`;

// Components can import and use the fragment
function DogCard({ dog }) {
  return (
    <Card>
      <Image src={dog.displayImage} />
      <Heading>{dog.name}</Heading>
      <Text>{dog.breed}</Text>
      <Text>{dog.description}</Text>
    </Card>
  );
}

Common Challenges and Solutions

ChallengeSolution
Stale dataUse fetchPolicy: ‘network-only’ or ‘cache-and-network’
Cache inconsistencyUse cache.modify() to update related entities
Deep object updatesUse deep merge strategy in typePolicies
Race conditionsImplement concurrency control with versioning
Authentication expirationUse Apollo Link to refresh tokens
Server schema changesUpdate client-side queries and fragments
Large response sizeUse GraphQL pagination and fragment optimization
Network failuresImplement retry strategies with RetryLink

Best Practices

  1. Organize GraphQL Operations: Store queries and mutations in separate files or near the components that use them.

  2. Use Fragments: Break down complex queries into reusable fragments to improve component isolation.

  3. Colocate Data Requirements: Define data needs close to the components that use them.

  4. Consistent Error Handling: Develop consistent error handling patterns across your application.

  5. Optimize Bundle Size: Use fine-grained imports to reduce bundle size:

    // Good
    import { useQuery } from '@apollo/client';
    
    // Better for tree-shaking (older versions)
    import { useQuery } from '@apollo/client/react/hooks';
    
  6. Avoid Overfetching: Request only the fields you need in each query.

  7. Testing Strategy: Use MockedProvider for unit and integration tests:

    import { MockedProvider } from '@apollo/client/testing';
    
    const mocks = [
      {
        request: {
          query: GET_DOGS,
          variables: { breed: 'Poodle' }
        },
        result: {
          data: {
            dogs: [{ id: '1', name: 'Buck', breed: 'Poodle' }]
          }
        }
      }
    ];
    
    render(
      <MockedProvider mocks={mocks} addTypename={false}>
        <DogList breed="Poodle" />
      </MockedProvider>
    );
    
  8. Manage Local State: Use reactive variables for simpler global state management.

  9. Implement Proper Authentication: Secure GraphQL endpoints and handle tokens appropriately.

  10. Monitor Performance: Track query performance and optimize slow queries.

Resources for Further Learning

Scroll to Top