GraphQL with React and Apollo Client
Apollo Client is the most widely used library for connecting a React application to a GraphQL API. It handles sending queries, caching results, updating the UI when data changes, and managing loading and error states — all with minimal boilerplate.
Installation
npm install @apollo/client graphql
Setting Up the Apollo Provider
Wrap your entire React app in an ApolloProvider. This makes the Apollo Client instance available to every component in the tree.
// main.jsx (entry point)
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql', ← Your GraphQL server URL
cache: new InMemoryCache(), ← Built-in normalized cache
});
ReactDOM.createRoot(document.getElementById('root')).render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
Fetching Data with useQuery
useQuery runs a query when the component mounts and returns loading, error, and data states. React re-renders automatically when the data arrives.
import { useQuery, gql } from '@apollo/client';
const GET_PRODUCTS = gql`
query GetProducts {
products {
id
name
price
}
}
`;
function ProductList() {
const { loading, error, data } = useQuery(GET_PRODUCTS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.products.map(product => (
<li key={product.id}>
{product.name} — ${product.price}
</li>
))}
</ul>
);
}
Querying with Variables
const GET_PRODUCT = gql`
query GetProduct($id: ID!) {
product(id: $id) {
name
price
description
}
}
`;
function ProductDetail({ productId }) {
const { loading, error, data } = useQuery(GET_PRODUCT, {
variables: { id: productId },
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error loading product</p>;
const { name, price, description } = data.product;
return (
<div>
<h2>{name}</h2>
<p>${price}</p>
<p>{description}</p>
</div>
);
}
Writing Data with useMutation
useMutation returns a function you call when the user triggers an action. It also returns loading and error states for the mutation.
import { useMutation, gql } from '@apollo/client';
const ADD_TO_CART = gql`
mutation AddToCart($productId: ID!) {
addToCart(productId: $productId) {
id
items { product { name } quantity }
}
}
`;
function AddToCartButton({ productId }) {
const [addToCart, { loading, error }] = useMutation(ADD_TO_CART);
return (
<>
<button
onClick={() => addToCart({ variables: { productId } })}
disabled={loading}
>
{loading ? 'Adding...' : 'Add to Cart'}
</button>
{error && <p>Failed to add: {error.message}</p>}
</>
);
}
Automatic Cache Updates
Apollo Client caches query results by default. When a mutation changes data, you can update the cache so the UI reflects the change instantly without a full page refresh.
const [createPost] = useMutation(CREATE_POST, {
update(cache, { data: { createPost } }) {
cache.modify({
fields: {
posts(existingPosts = []) {
const newRef = cache.writeFragment({
data: createPost,
fragment: gql`fragment NewPost on Post { id title }`
});
return [...existingPosts, newRef];
}
}
});
}
});
Key Points
- Wrap your app in
ApolloProviderwith anApolloClientinstance to make GraphQL available everywhere. useQueryfetches data on mount and returnsloading,error, anddata.useMutationreturns a function to trigger the mutation and tracks loading/error states.- Apollo Client caches query results automatically; mutations can update the cache to keep the UI in sync.
- The
gqltemplate literal tag parses query strings into the AST format Apollo needs.
