Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to use Apollo Client in React

Apollo Client is the most powerful GraphQL client for React, providing intelligent caching, optimistic updates, subscriptions, and error handling out of the box. As the creator of CoreUI with 25 years of web development experience, I use Apollo Client in data-intensive React dashboards where normalized caching significantly reduces redundant API calls and keeps the UI in sync across components. Beyond useQuery and useMutation, Apollo’s real power lies in its normalized cache — when any component fetches a user, every other component showing that user updates automatically. Understanding the cache is what separates basic Apollo usage from production-grade Apollo usage.

Set up Apollo Client with authentication and error handling.

// apolloClient.js
import {
  ApolloClient,
  InMemoryCache,
  createHttpLink,
  from,
  ApolloLink
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { onError } from '@apollo/client/link/error'

const httpLink = createHttpLink({ uri: '/graphql' })

const authLink = setContext((_, { headers }) => ({
  headers: {
    ...headers,
    authorization: localStorage.getItem('token')
      ? `Bearer ${localStorage.getItem('token')}`
      : ''
  }
}))

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    graphQLErrors.forEach(({ message, extensions }) => {
      if (extensions?.code === 'UNAUTHENTICATED') {
        localStorage.removeItem('token')
        window.location.href = '/login'
      }
      console.error(`GraphQL Error: ${message}`)
    })
  }
  if (networkError) {
    console.error(`Network Error: ${networkError}`)
  }
})

export const client = new ApolloClient({
  link: from([errorLink, authLink, httpLink]),
  cache: new InMemoryCache()
})

from([errorLink, authLink, httpLink]) chains links in order. Error handling runs first, then auth headers are added, then the HTTP request is made. The onError link intercepts GraphQL and network errors globally.

Optimistic Updates

Update the UI immediately before the server responds.

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

const TOGGLE_LIKE = gql`
  mutation ToggleLike($postId: ID!) {
    toggleLike(postId: $postId) {
      id
      liked
      likeCount
    }
  }
`

function LikeButton({ postId, liked, likeCount }) {
  const [toggleLike] = useMutation(TOGGLE_LIKE, {
    variables: { postId },
    optimisticResponse: {
      toggleLike: {
        __typename: 'Post',
        id: postId,
        liked: !liked,
        likeCount: liked ? likeCount - 1 : likeCount + 1
      }
    }
  })

  return (
    <button onClick={() => toggleLike()}>
      {liked ? '❤️' : '🤍'} {likeCount}
    </button>
  )
}

optimisticResponse provides an expected result that Apollo writes to the cache immediately. If the mutation succeeds, the real response replaces it. If it fails, Apollo rolls back to the previous state automatically.

Pagination with fetchMore

Load more items on demand.

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

const GET_POSTS = gql`
  query GetPosts($offset: Int!, $limit: Int!) {
    posts(offset: $offset, limit: $limit) {
      id
      title
      author
    }
    postCount
  }
`

function PostList() {
  const { data, loading, fetchMore } = useQuery(GET_POSTS, {
    variables: { offset: 0, limit: 10 }
  })

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

  return (
    <div>
      {data?.posts.map(p => <div key={p.id}>{p.title}</div>)}
      {data?.posts.length < data?.postCount && (
        <button onClick={loadMore} disabled={loading}>Load More</button>
      )}
    </div>
  )
}

fetchMore makes an additional query and merges the results using updateQuery. This pattern implements infinite scroll or “load more” pagination without replacing the existing results.

Best Practice Note

This is the advanced Apollo usage pattern referenced in CoreUI React dashboard templates. For the cache to work correctly, every __typename + id combination must be unique across your API — Apollo uses this for normalization. Configure InMemoryCache with typePolicies when your types use a non-id primary key or require custom merge strategies. See how to use GraphQL in React for the basic setup, including the plain fetch alternative.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team