- Understanding Apollo Client and GraphQL
- Setting Up Apollo Client
- Fetching Data with Apollo Client
- Managing Local State with Apollo Client
- Optimizing Performance with Apollo Client
- Error Handling with Apollo Client
- Managing Pagination with Apollo Client
- Integrating Apollo Client with State Management Libraries
- Working with Mutations in Apollo Client
- Subscriptions with Apollo Client
- Server-Side Rendering with Apollo Client
- Using Apollo Client with TypeScript
- Advanced Features and Customization
- Conclusion
In the modern web development landscape, managing data efficiently and effectively is crucial. GraphQL has emerged as a powerful alternative to REST, offering a more flexible and efficient way to fetch and manage data. Apollo Client is a popular JavaScript library that simplifies working with GraphQL, providing tools to query, cache, and manage local state. This article will guide you through using Apollo Client in JavaScript applications, from setup to advanced features, ensuring you can leverage its full potential in your projects.
Understanding Apollo Client and GraphQL
Before diving into the implementation, it’s essential to understand what Apollo Client and GraphQL are and how they work together to enhance your application.
What is GraphQL?

GraphQL is a query language for APIs, allowing clients to request exactly the data they need. It was developed by Facebook and provides a more efficient, powerful, and flexible alternative to REST.
With GraphQL, you can query multiple resources in a single request, reducing the number of API calls and simplifying data management.
What is Apollo Client?

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. It simplifies the process of fetching, caching, and synchronizing data across your application.
Apollo Client works seamlessly with any GraphQL server and integrates easily with modern JavaScript frameworks like React, Angular, and Vue.
Why Use Apollo Client?
Apollo Client offers several advantages for managing data in JavaScript applications:
- Declarative Data Fetching: Apollo Client uses declarative queries, making it easier to understand and maintain your data fetching logic.
- Optimized Performance: With built-in caching and intelligent query management, Apollo Client minimizes network requests and improves application performance.
- Flexibility: Apollo Client supports various platforms and can be used with or without a framework.
- Developer Experience: Apollo Client provides excellent tooling, including a powerful devtools extension, which enhances the developer experience.
Setting Up Apollo Client
To start using Apollo Client in your JavaScript application, you’ll need to set up your development environment. This involves installing the necessary packages and configuring Apollo Client to connect to your GraphQL server.
Installing Apollo Client
First, install the Apollo Client package and its dependencies using npm or yarn. For this example, we’ll use npm.
npm install @apollo/client graphql
The @apollo/client
package includes everything you need to set up Apollo Client, while the graphql
package is the core library for working with GraphQL.
Initializing Apollo Client
Next, initialize Apollo Client in your application. Create an Apollo Client instance and configure it to connect to your GraphQL server.
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({
uri: 'https://your-graphql-endpoint.com/graphql',
}),
cache: new InMemoryCache(),
});
In this example, HttpLink
is used to define the GraphQL endpoint, and InMemoryCache
is used to manage the client-side cache.
Integrating Apollo Client with Your Application
Once you have initialized Apollo Client, you need to integrate it with your application. If you’re using React, you can use the ApolloProvider
component to make the client available throughout your component tree.
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from '@apollo/client';
import App from './App';
import client from './apollo-client';
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
In this example, ApolloProvider
wraps your entire application, making the Apollo Client instance available to all components.
Fetching Data with Apollo Client
One of the primary use cases for Apollo Client is fetching data from a GraphQL server. Apollo Client makes this process straightforward and efficient.
Writing GraphQL Queries

To fetch data with Apollo Client, you need to write GraphQL queries. A query defines the data you want to fetch from the server. Here’s an example of a simple GraphQL query:
query GetBooks {
books {
id
title
author
}
}
This query requests the id
, title
, and author
fields for all books.
Fetching Data in React Components
To fetch data in a React component, use the useQuery
hook provided by Apollo Client. This hook executes the query and returns the result, along with loading and error states.
import React from 'react';
import { useQuery, gql } from '@apollo/client';
const GET_BOOKS = gql`
query GetBooks {
books {
id
title
author
}
}
`;
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}>
{book.title} by {book.author}
</li>
))}
</ul>
);
}
export default BookList;
In this example, the useQuery
hook is used to execute the GET_BOOKS
query. The component renders a loading message while the query is in progress, an error message if the query fails, and the list of books if the query succeeds.
Managing Local State with Apollo Client
In addition to fetching data from a GraphQL server, Apollo Client can manage local state in your application. This capability allows you to consolidate your local and remote data management into a single framework, simplifying your codebase and improving maintainability.
Combining Local and Remote Data

Apollo Client enables you to combine local and remote data seamlessly. You can define local state fields in your GraphQL queries and manage them using the Apollo Client cache.
Defining Local State
To define local state, extend your GraphQL schema with client-specific fields. Use the @client
directive to indicate that a field is managed locally.
query GetBooks {
books {
id
title
author
}
isLoggedIn @client
}
In this example, isLoggedIn
is a local state field managed by Apollo Client.
Managing Local State in the Cache

You can initialize and update local state using Apollo Client’s cache. Define initial state values when creating the client.
const cache = new InMemoryCache();
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' }),
cache,
resolvers: {
Query: {
isLoggedIn: () => {
return !!localStorage.getItem('token');
},
},
},
});
cache.writeQuery({
query: gql`
query GetLocalState {
isLoggedIn @client
}
`,
data: {
isLoggedIn: !!localStorage.getItem('token'),
},
});
In this example, the isLoggedIn
field is initialized based on the presence of a token in local storage.
Updating Local State
You can update local state using Apollo Client’s writeQuery
or writeFragment
methods. These methods allow you to modify the cache directly.
function login() {
localStorage.setItem('token', 'your-token');
client.writeQuery({
query: gql`
query GetLocalState {
isLoggedIn @client
}
`,
data: {
isLoggedIn: true,
},
});
}
function logout() {
localStorage.removeItem('token');
client.writeQuery({
query: gql`
query GetLocalState {
isLoggedIn @client
}
`,
data: {
isLoggedIn: false,
},
});
}
In this example, the login
and logout
functions update the isLoggedIn
field in the cache.
Reactive Local State with Reactive Variables
Reactive variables in Apollo Client provide another way to manage local state. Reactive variables automatically trigger updates in components that depend on them, ensuring that your UI stays in sync with your application state.
Defining Reactive Variables
Define reactive variables using the makeVar
function.
import { makeVar } from '@apollo/client';
const isLoggedInVar = makeVar(!!localStorage.getItem('token'));
Using Reactive Variables in Queries
Use reactive variables in your GraphQL queries by defining client-side resolvers.
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
isLoggedIn: {
read() {
return isLoggedInVar();
},
},
},
},
},
});
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' }),
cache,
});
Updating Reactive Variables
Update reactive variables directly, and Apollo Client will automatically update any components that depend on these variables.
function login() {
localStorage.setItem('token', 'your-token');
isLoggedInVar(true);
}
function logout() {
localStorage.removeItem('token');
isLoggedInVar(false);
}
In this example, the login
and logout
functions update the isLoggedInVar
reactive variable.
Optimizing Performance with Apollo Client
Optimizing the performance of your Apollo Client setup is crucial for ensuring a smooth user experience. Apollo Client provides several features to help you achieve this, including caching, batching, and query optimization.
Efficient Caching
Caching is a core feature of Apollo Client, significantly reducing the number of network requests and improving data fetching performance.
Normalized Caching
Apollo Client uses a normalized cache to store and retrieve data efficiently. This cache stores data by unique identifiers, allowing for quick lookups and updates.
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
book: {
read(existing, { args, toReference }) {
return existing || toReference({ __typename: 'Book', id: args.id });
},
},
},
},
},
});
Fine-Tuning Cache Policies
You can fine-tune cache policies to control how data is read and written to the cache. This includes setting policies for specific queries, fields, and types.
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
books: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
},
});
Query Batching
Apollo Client supports query batching, which groups multiple queries into a single network request. This reduces the number of network requests and improves performance, especially when dealing with multiple small queries.
import { ApolloLink, HttpLink, BatchHttpLink } from '@apollo/client';
const httpLink = new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' });
const batchLink = new BatchHttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' });
const client = new ApolloClient({
link: ApolloLink.split(
operation => operation.getContext().batch,
batchLink,
httpLink
),
cache: new InMemoryCache(),
});
Query Optimization
Optimizing your queries can have a significant impact on performance. Ensure that your queries are efficient by requesting only the data you need and avoiding unnecessary complexity.
Using Fragments

Fragments allow you to reuse pieces of queries and ensure consistency across different parts of your application.
fragment BookDetails on Book {
id
title
author
}
query GetBooks {
books {
...BookDetails
}
}
query GetBook($id: ID!) {
book(id: $id) {
...BookDetails
}
}
In this example, the BookDetails
fragment is reused in multiple queries, ensuring consistency and reducing redundancy.
Error Handling with Apollo Client

Effective error handling is crucial for maintaining a robust and user-friendly application. Apollo Client provides tools to manage and respond to errors in your GraphQL queries and mutations, ensuring your application can handle issues gracefully.
Handling Errors in Queries and Mutations
Apollo Client uses a unified approach to handle errors in both queries and mutations. Errors can be caught and managed using the error
property returned by hooks like useQuery
and useMutation
.
Handling Errors in Queries
When using the useQuery
hook, you can access the error
property to handle any errors that occur during the execution of your query.
import React from 'react';
import { useQuery, gql } from '@apollo/client';
const GET_BOOKS = gql`
query GetBooks {
books {
id
title
author
}
}
`;
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}>
{book.title} by {book.author}
</li>
))}
</ul>
);
}
export default BookList;
In this example, the error
property is used to display an error message if the query fails.
Handling Errors in Mutations
Similarly, when using the useMutation
hook, you can access the error
property to handle errors that occur during the execution of your mutation.
import React from 'react';
import { useMutation, gql } from '@apollo/client';
const ADD_BOOK = gql`
mutation AddBook($title: String!, $author: String!) {
addBook(title: $title, author: $author) {
id
title
author
}
}
`;
function AddBook() {
let title, author;
const [addBook, { error }] = useMutation(ADD_BOOK);
const handleSubmit = e => {
e.preventDefault();
addBook({ variables: { title: title.value, author: author.value } });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Title"
ref={node => { title = node; }}
/>
<input
type="text"
placeholder="Author"
ref={node => { author = node; }}
/>
<button type="submit">Add Book</button>
{error && <p>Error: {error.message}</p>}
</form>
);
}
export default AddBook;
In this example, the error
property is used to display an error message if the mutation fails.
Global Error Handling
In addition to handling errors locally within components, you can set up global error handling using Apollo Client’s link functionality. This allows you to catch and manage errors at a higher level, ensuring consistent error handling across your application.
Setting Up Error Link
Use the apollo-link-error
package to create an error link that handles errors globally.
npm install @apollo/client @apollo/link-error
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client';
import { onError } from '@apollo/link-error';
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}`);
});
const httpLink = new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' });
const client = new ApolloClient({
link: ApolloLink.from([errorLink, httpLink]),
cache: new InMemoryCache(),
});
In this example, the errorLink
catches both GraphQL and network errors and logs them to the console. You can customize the error handling logic to suit your needs, such as displaying error notifications to users or sending error reports to an external service.
Retrying Failed Requests
Sometimes, transient errors like network issues can cause requests to fail. Apollo Client provides mechanisms to automatically retry failed requests, improving the resilience of your application.
Setting Up Retry Link
Use the apollo-link-retry
package to create a retry link that retries failed requests.
npm install @apollo/client @apollo/link-retry
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client';
import { RetryLink } from '@apollo/link-retry';
const retryLink = new RetryLink({
attempts: {
max: 3,
retryIf: (error, _operation) => !!error,
},
});
const httpLink = new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' });
const client = new ApolloClient({
link: ApolloLink.from([retryLink, httpLink]),
cache: new InMemoryCache(),
});
In this example, the retryLink
retries failed requests up to three times. You can customize the retry logic to suit your needs, such as implementing exponential backoff or handling specific error codes differently.
Managing Pagination with Apollo Client

Pagination is a common requirement in applications that deal with large datasets. Apollo Client provides tools to handle pagination efficiently, ensuring smooth and performant data fetching.
Cursor-Based Pagination
Cursor-based pagination is a popular approach where each item has a cursor, and you fetch the next set of items using the cursor of the last item from the previous set.
Query for Cursor-Based Pagination
Define a GraphQL query that supports cursor-based pagination.
query GetBooks($cursor: String) {
books(cursor: $cursor) {
edges {
node {
id
title
author
}
cursor
}
pageInfo {
hasNextPage
}
}
}
Fetching Paginated Data in React Components
Use the useQuery
hook to fetch paginated data and manage the cursor state.
import React, { useState } from 'react';
import { useQuery, gql } from '@apollo/client';
const GET_BOOKS = gql`
query GetBooks($cursor: String) {
books(cursor: $cursor) {
edges {
node {
id
title
author
}
cursor
}
pageInfo {
hasNextPage
}
}
}
`;
function BookList() {
const [cursor, setCursor] = useState(null);
const { loading, error, data, fetchMore } = useQuery(GET_BOOKS, {
variables: { cursor },
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
const loadMore = () => {
fetchMore({
variables: {
cursor: data.books.edges[data.books.edges.length - 1].cursor,
},
}).then(fetchMoreResult => {
setCursor(fetchMoreResult.data.books.edges[fetchMoreResult.data.books.edges.length - 1].cursor);
});
};
return (
<div>
<ul>
{data.books.edges.map(({ node }) => (
<li key={node.id}>
{node.title} by {node.author}
</li>
))}
</ul>
{data.books.pageInfo.hasNextPage && <button onClick={loadMore}>Load More</button>}
</div>
);
}
export default BookList;
In this example, the fetchMore
function is used to fetch the next set of items when the “Load More” button is clicked. The cursor state is updated to keep track of the pagination.
Integrating Apollo Client with State Management Libraries

For larger applications, integrating Apollo Client with state management libraries like Redux or MobX can provide more robust state management solutions. Apollo Client can work seamlessly with these libraries, allowing you to manage both local and remote data efficiently.
Using Apollo Client with Redux
Redux is a popular state management library for JavaScript applications. You can integrate Apollo Client with Redux to manage your application’s state more effectively.
Setting Up Apollo Client with Redux
Install the necessary packages:
npm install @apollo/client redux
Configure Apollo Client to use Redux for state management.
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { createStore, combineReducers } from 'redux';
import { ApolloReducerConfig, apolloReducer } from 'apollo-cache-redux';
const cache = new InMemoryCache({
reducerConfig: {
dataIdFromObject: object => object.id || null,
},
});
const store = createStore(
combineReducers({
apollo: apolloReducer,
})
);
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' }),
cache,
});
Using Apollo Client in Redux-Aware Components

You can now use Apollo Client in your Redux-aware components, leveraging Redux for state management while using Apollo Client for data fetching.
import React from 'react';
import { Provider } from 'react-redux';
import { ApolloProvider } from '@apollo/client';
import store from './store';
import client from './apollo-client';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<Apollo
Provider client={client}>
<App />
</ApolloProvider>
</Provider>,
document.getElementById('root')
);
In this example, both the Redux store and Apollo Client are provided to your application, allowing you to manage state and data fetching seamlessly.
Using Apollo Client with MobX
MobX is another state management library that provides a more reactive approach. You can integrate Apollo Client with MobX to create a reactive data fetching and state management solution.
Setting Up Apollo Client with MobX
Install the necessary packages:
npm install @apollo/client mobx mobx-react
Configure Apollo Client and MobX to work together.
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { makeAutoObservable } from 'mobx';
class Store {
isLoggedIn = !!localStorage.getItem('token');
constructor() {
makeAutoObservable(this);
}
login() {
localStorage.setItem('token', 'your-token');
this.isLoggedIn = true;
}
logout() {
localStorage.removeItem('token');
this.isLoggedIn = false;
}
}
const store = new Store();
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' }),
cache: new InMemoryCache(),
});
Using Apollo Client in MobX-Aware Components
Use Apollo Client in your MobX-aware components, leveraging MobX for state management and Apollo Client for data fetching.
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { observer } from 'mobx-react';
import client from './apollo-client';
import store from './store';
import App from './App';
const Root = observer(() => (
<ApolloProvider client={client}>
<App store={store} />
</ApolloProvider>
));
ReactDOM.render(<Root />, document.getElementById('root'));
In this example, MobX is used to manage local state, while Apollo Client handles data fetching, providing a reactive and efficient state management solution.
Working with Mutations in Apollo Client

Mutations are used in GraphQL to modify data on the server. Apollo Client makes it easy to send mutations and handle responses, ensuring your application’s state stays in sync with the server.
Writing Mutations
To define a mutation in Apollo Client, use the gql
template literal to create a GraphQL mutation. This mutation can be used to create, update, or delete data.
mutation AddBook($title: String!, $author: String!) {
addBook(title: $title, author: $author) {
id
title
author
}
}
This mutation adds a new book with the specified title and author.
Executing Mutations in React Components
To execute mutations in a React component, use the useMutation
hook provided by Apollo Client. This hook returns a function that you can call to perform the mutation and an object containing the mutation’s state.
import React, { useState } from 'react';
import { useMutation, gql } from '@apollo/client';
const ADD_BOOK = gql`
mutation AddBook($title: String!, $author: String!) {
addBook(title: $title, author: $author) {
id
title
author
}
}
`;
function AddBook() {
const [title, setTitle] = useState('');
const [author, setAuthor] = useState('');
const [addBook, { loading, error, data }] = useMutation(ADD_BOOK);
const handleSubmit = e => {
e.preventDefault();
addBook({ variables: { title, author } });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Title"
value={title}
onChange={e => setTitle(e.target.value)}
/>
<input
type="text"
placeholder="Author"
value={author}
onChange={e => setAuthor(e.target.value)}
/>
<button type="submit" disabled={loading}>
Add Book
</button>
{error && <p>Error: {error.message}</p>}
{data && <p>Book added successfully!</p>}
</form>
);
}
export default AddBook;
In this example, the useMutation
hook is used to execute the ADD_BOOK
mutation. The component manages the state for the title and author fields and displays the mutation’s status.
Updating the Cache After Mutations
After performing a mutation, it’s often necessary to update the Apollo Client cache to ensure the UI reflects the latest data. This can be done using the update
function provided by the useMutation
hook.
const [addBook] = useMutation(ADD_BOOK, {
update(cache, { data: { addBook } }) {
cache.modify({
fields: {
books(existingBooks = []) {
const newBookRef = cache.writeFragment({
data: addBook,
fragment: gql`
fragment NewBook on Book {
id
title
author
}
`
});
return [...existingBooks, newBookRef];
}
}
});
}
});
In this example, the cache is updated to include the new book after the mutation is successful.
Subscriptions with Apollo Client
Subscriptions are used in GraphQL to listen for real-time updates from the server. Apollo Client supports subscriptions, enabling your application to react to data changes instantly.
Setting Up Subscriptions
To set up subscriptions, you’ll need a WebSocket link to handle the subscription operations. First, install the necessary packages:
npm install @apollo/client subscriptions-transport-ws
Configuring Apollo Client for Subscriptions
Create a WebSocket link and configure Apollo Client to use it.
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client';
import { WebSocketLink } from 'subscriptions-transport-ws';
import { getMainDefinition } from '@apollo/client/utilities';
const httpLink = new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' });
const wsLink = new WebSocketLink({
uri: 'ws://your-graphql-endpoint.com/graphql',
options: {
reconnect: true
}
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
In this example, the split
function directs subscription operations to the WebSocket link and other operations to the HTTP link.
Using Subscriptions in React Components
To use subscriptions in a React component, use the useSubscription
hook provided by Apollo Client.
import React from 'react';
import { useSubscription, gql } from '@apollo/client';
const BOOK_ADDED = gql`
subscription OnBookAdded {
bookAdded {
id
title
author
}
}
`;
function BookList() {
const { data, loading } = useSubscription(BOOK_ADDED);
if (loading) return <p>Loading...</p>;
return (
<div>
<h2>New Book Added</h2>
{data && (
<p>
{data.bookAdded.title} by {data.bookAdded.author}
</p>
)}
</div>
);
}
export default BookList;
In this example, the useSubscription
hook listens for new books added and updates the UI accordingly.
Server-Side Rendering with Apollo Client
Server-side rendering (SSR) can improve the performance and SEO of your application by rendering the initial HTML on the server. Apollo Client supports SSR, making it possible to fetch data and render components on the server.
Setting Up Apollo Client for SSR
To set up Apollo Client for SSR, you’ll need to use ApolloProvider
and getDataFromTree
from the @apollo/client
package. First, install the necessary packages:
npm install @apollo/client react-dom-server
Configuring Apollo Client for SSR
Configure Apollo Client to fetch data and render components on the server.
import React from 'react';
import { ApolloProvider, getDataFromTree } from '@apollo/client';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import App from './App';
import createApolloClient from './createApolloClient';
export default async function render(req, res) {
const client = createApolloClient();
const context = {};
const AppWithProvider = (
<ApolloProvider client={client}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</ApolloProvider>
);
await getDataFromTree(AppWithProvider);
const content = renderToString(AppWithProvider);
const initialState = client.extract();
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Apollo SSR</title>
</head>
<body>
<div id="root">${content}</div>
<script>
window.__APOLLO_STATE__ = ${JSON.stringify(initialState).replace(/</g, '\\u003c')}
</script>
<script src="/bundle.js"></script>
</body>
</html>
`);
}
In this example, getDataFromTree
is used to fetch all data needed by the components before rendering them on the server. The initialState
is then sent to the client to hydrate the Apollo Client cache.
Hydrating Apollo Client on the Client-Side
On the client side, you need to hydrate Apollo Client with the initial state sent from the server.
import React from 'react';
import { ApolloClient, InMemoryCache, HttpLink, ApolloProvider } from '@apollo/client';
import { hydrate } from 'react-dom';
import App from './App';
const cache = new InMemoryCache().restore(window.__APOLLO_STATE__);
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://your-graphql-endpoint.com/graphql' }),
cache,
});
hydrate(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
In this example, the initial state is restored to Apollo Client’s cache, ensuring that the client-side rendering is in sync with the server-rendered HTML.
Using Apollo Client with TypeScript
Using TypeScript with Apollo Client can enhance your development experience by providing type safety and better tooling. Apollo Client fully supports TypeScript, allowing you to define types for your queries, mutations, and components.
Setting Up TypeScript with Apollo Client
To set up TypeScript with Apollo Client, install the necessary packages:
npm install @apollo/client graphql typescript @types/node @types/react
Defining Types for GraphQL Operations
Use the graphql-code-generator
to automatically generate TypeScript types for your GraphQL operations. First, install the generator:
npm install @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
Configuring Code Generation
Create a codegen.yml
file to configure the code generator:
schema: https://your-graphql-endpoint.com/graphql
documents: './src/**/*.graphql'
generates:
./src
/generated/graphql.tsx:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-react-apollo'
Running Code Generation
Run the code generator to generate TypeScript types for your GraphQL operations:
npx graphql-codegen
Using Generated Types in Your Components
Import the generated types and use them in your components to ensure type safety.
import React from 'react';
import { useQuery } from '@apollo/client';
import { GetBooksQuery, GetBooksQueryVariables } from './generated/graphql';
import { GET_BOOKS } from './queries';
function BookList() {
const { loading, error, data } = useQuery<GetBooksQuery, GetBooksQueryVariables>(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}>
{book.title} by {book.author}
</li>
))}
</ul>
);
}
export default BookList;
In this example, the useQuery
hook is typed with the generated GetBooksQuery
and GetBooksQueryVariables
types, ensuring that the data and variables match the expected types.
Advanced Features and Customization
Apollo Client offers a range of advanced features and customization options to meet the specific needs of your application.
Custom Fetch Policies
Fetch policies determine how Apollo Client retrieves data from the cache and the server. You can customize fetch policies to control the behavior of your queries.
const { loading, error, data } = useQuery(GET_BOOKS, {
fetchPolicy: 'cache-and-network'
});
In this example, the cache-and-network
fetch policy retrieves data from the cache first and then fetches the latest data from the server.
Customizing Cache
Apollo Client’s cache can be customized to handle specific data structures and use cases. You can define custom cache policies and merge functions to control how data is stored and retrieved.
const cache = new InMemoryCache({
typePolicies: {
Book: {
keyFields: ['id'],
fields: {
title: {
merge: false
}
}
}
}
});
In this example, the Book
type uses id
as the key field, and the title
field is not merged when updating the cache.
Using Apollo Client with Other Data Sources
Apollo Client can be used with multiple data sources, allowing you to combine data from different APIs and local state.
import { ApolloClient, InMemoryCache, ApolloLink, HttpLink } from '@apollo/client';
import { onError } from '@apollo/link-error';
const httpLink1 = new HttpLink({ uri: 'https://api1.example.com/graphql' });
const httpLink2 = new HttpLink({ uri: 'https://api2.example.com/graphql' });
const link = ApolloLink.split(
operation => operation.getContext().clientName === 'api2',
httpLink2,
httpLink1
);
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
In this example, Apollo Client is configured to use different HTTP links based on the context of the operation, allowing it to fetch data from multiple APIs.
Optimistic UI Updates
Optimistic UI updates allow you to immediately reflect changes in the UI before the server confirms the mutation. This improves the user experience by providing instant feedback.
const [addBook] = useMutation(ADD_BOOK, {
optimisticResponse: {
addBook: {
__typename: 'Book',
id: -1,
title: 'Temporary Title',
author: 'Temporary Author'
}
},
update(cache, { data: { addBook } }) {
cache.modify({
fields: {
books(existingBooks = []) {
const newBookRef = cache.writeFragment({
data: addBook,
fragment: gql`
fragment NewBook on Book {
id
title
author
}
`
});
return [...existingBooks, newBookRef];
}
}
});
}
});
In this example, an optimistic response is provided for the ADD_BOOK
mutation, immediately updating the UI with the new book details.
Conclusion
Apollo Client is a versatile and powerful tool for managing data in JavaScript applications. From fetching and caching data to managing local state and handling errors, Apollo Client provides comprehensive solutions that enhance your application’s performance and developer experience. By leveraging Apollo Client’s advanced features and following best practices, businesses can create robust, scalable, and user-friendly applications. Whether you are building a simple web app or a complex enterprise solution, Apollo Client has the tools you need to succeed.
Read Next: