Apollo Client is the standard GraphQL client for React. It handles fetching, caching, and updating data with minimal boilerplate.
Setup
npm install @apollo/client graphqlWrap 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>