Skip to main content
GraphQL Fundamentals·Lesson 4 of 5

Apollo Client & React

Apollo Client is the standard GraphQL client for React. It handles fetching, caching, and updating data with minimal boilerplate.

Setup

npm install @apollo/client graphql

Wrap your app in ApolloProvider:

// main.jsx
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

const client = new ApolloClient({
  uri:   'http://localhost:4000/graphql',
  cache: new InMemoryCache(),
});

ReactDOM.createRoot(document.getElementById('root')).render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);

Querying with useQuery

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

const GET_BOOKS = gql`
  query GetBooks {
    books {
      id
      title
      author
      year
    }
  }
`;

function BookList() {
  const { loading, error, data } = useQuery(GET_BOOKS);

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

  return (
    <ul>
      {data.books.map(book => (
        <li key={book.id}>
          <strong>{book.title}</strong>  {book.author} ({book.year})
        </li>
      ))}
    </ul>
  );
}

Mutations with useMutation

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

const ADD_BOOK = gql`
  mutation AddBook($title: String!, $author: String!, $year: Int) {
    addBook(title: $title, author: $author, year: $year) {
      id title author year
    }
  }
`;

function AddBookForm() {
  const [addBook, { loading }] = useMutation(ADD_BOOK, {
    refetchQueries: [{ query: GET_BOOKS }],
  });
  const [title, setTitle]   = useState('');
  const [author, setAuthor] = useState('');

  function handleSubmit(e) {
    e.preventDefault();
    addBook({ variables: { title, author, year: 2026 } });
    setTitle(''); setAuthor('');
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={title}  onChange={e=> setTitle(e.target.value)}  placeholder="Title"  required />
      <input value={author} onChange={e=> setAuthor(e.target.value)} placeholder="Author" required />
      <button type="submit" disabled={loading}>Add Book</button>
    </form>
  );
}

refetchQueries re-runs GET_BOOKS after the mutation so the list updates automatically.

What the UI Looks Like

Ctrl+Enter
HTML
CSS
JS
Preview

Caching

Apollo Client's InMemoryCache normalises responses by __typename + id. After a query fetches a book with id: "1", any other query that returns the same book gets it from cache instantly — no network request.

After a mutation, tell Apollo how to update the cache:

const [deleteBook] = useMutation(DELETE_BOOK, {
  update(cache, { data: { deleteBook: id } }) {
    cache.modify({
      fields: {
        books(existing, { readField }) {
          return existing.filter(ref => readField('id', ref) !== id)
        }
      }
    })
  }
})

Query Variables and Polling

// With variables
const { data } = useQuery(GET_BOOK, {
  variables: { id: bookId },
});

// Poll every 5 seconds
const { data } = useQuery(GET_BOOKS, {
  pollInterval: 5000,
});

// Refetch manually
const { data, refetch } = useQuery(GET_BOOKS);
<button onClick={()=> refetch()}>Refresh</button>