TABLE OF CONTENTS

React Apollo: What Is It & How to Use It in 2022

What is The ApolloClient

The React ApolloClient is a fully functional GraphQL client and state management library for JavaScript.

We can use it to manage local and remote data by fetching and modifying them. ApolloClient has React integration by default. ApolloClient helps you structure the code, making it more readable and clean, and as a result, making the ReactJS app development much smoother.

In this article, I’m going to discuss the ApolloClient’s basics, show you how to take advantage of it in your projects, and show you its hooks work.

Features

  • Ease of writing queries and controlling responses.
  • It works with all popular JS libraries and frameworks, as well as Typescript, and has a browser extension, which makes it easier to work with hooks.
  • Created for modern javascript
  • Big community
  • Documentation with many examples of the use of ApolloClient features

ApolloClient chrome extension

Before I dive into hooks, I recommend installing the ApolloClient Extension plugin for chrome or firefox. The plugin acts as a debugging tool and it will help you work with GraphQL queries.

The main advantages of the extension are:

  • built-in Apollo Studio Explorer. This allows you to execute queries on the GraphQL server by using the application interface
  • the mutation inspector that displays the introduced mutations
  • access to queries – when are they loaded and what variables they use

How To add Apollo Client to your React App

Let’s start by adding the ApolloClient to the React project. We’ll go through this step by step.

  1. Create a new Create React App project.
npx create-react-app apollo-with-react
  1. Install ApolloClient and GraphQL in the directory apollo-with-react project.
yarn add @apollo/client graphql
  1. Initialize ApolloClient – by passing a configuration object containing the URI and cache fields.

As URI we will use – https://graphql-compose.herokuapp.com/northwind/ – sample data of a random trading company.

Create a new directory – apollo-with-react/apollo/index.js and add:

import { ApolloClient, InMemoryCache } from '@apollo/client'
 
export const client = new ApolloClient({
 uri: 'https://graphql-compose.herokuapp.com/northwind/',
 cache: new InMemoryCache()
})
  • URI is the URL of our GraphQL server.
  • The cache is an InMemoryCache instance. Apollo uses it to cache the results of queries after they are downloaded

That’s it. Our client is now ready to fetch the data. But before we start getting the first data, we need to connect the ApolloClient to React.

  1. Connect Apollo to the React app. 

In order to use clients and hooks in React, we’re going to need an ApolloProvider. We need to wrap our entire application in ApolloProvider and put the Apollo client in context.

This will grant us access to Apollo at every level of the application. I recommend wrapping the application as close to the top of the code as possible:

import { ApolloProvider } from '@apollo/client'
ReactDOM.render(
 <ApolloProvider client={client}>
   <App />
 </ApolloProvider>,
 document.getElementById('root')
)
const Todos = ({ todos, generationDate }) => {
  return (
    <>
      <h3>Generated on: {generationDate}</h3>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <Link href={`todos/${todo.id}`}>{todo.title}</Link>
          </li>
        ))}
      </ul>
    </>
  );
};

export async function getStaticProps() {
  const filePath = path.join(process.cwd(), "data", "todos.json");
  const todosData = fs.readFileSync(filePath, "utf8");

  return {
    props: {
      todos: JSON.parse(todosData).todos.map((todo) => ({
        id: todo.id,
        title: todo.title,
      })),
      generationDate: new Date().toLocaleTimeString(),
    },
  };
}

export default Todos;

And Voilà.

Now you’re able to write the first query. Next part, I will introduce you to ApolloClient Hooks, thanks to which you will be able to execute queries and mutations. 

The final effect with all examples can be found on codesandbox – the link is at the end of the article.

APOLLO HOOKS

Apollo Hooks are an easier way to work with data in React. They enable the fetch and transfer of data.

Apollo useQuery

The useQuery hook is a function used to fetch data.

We’ve used the https://graphql-compose.herokuapp.com/northwind/ URI when setting up the ApolloClient and now we can write our first query.

First, create a new component and a file containing the query

  • src/components/GetProductList/query.js
  • src/components/GetProductList/index.js

query.js file:

import { gql } from '@apollo/client'
 
export const GET_PRODUCT_LIST = gql`
 query GetProductList {
   viewer {
     productList {
       productID
       name
       category {
         categoryID
         name
       }
     }
   }
 }
`

Then, create a component in index.js and import the query GET_PRODUCT_LIST created above. Use it as follows and you will be able to view a product list with product names and categories.

import { useQuery } from '@apollo/client'
import { GET_PRODUCT_LIST } from './query'
 
const ProductList = () => {
 const { loading, error, data } = useQuery(GET_PRODUCT_LIST)
 
 if (loading) return <span>Loading.</span>
 if (error) return <span>Error.</span>
 
 return (
   <ul>
     {data.viewer.productList.map(({ productID, name, category }) => (
       <li key={productID}>
         <p>{name}</p>
         {category && (
           <span>
             <strong>category:</strong> {category.name}
           </span>
         )}
       </li>
     ))}
   </ul>
 )
}
 
export default ProductList

Fetch policy

The useQuery hook checks Apollo cache by default – to see if all requested data is available locally. If data is available, useQuery returns this data and doesn’t query the GraphQL server.

We can specify the download policy for the query via the fetchPolicy option:

const { loading, error, data } = useQuery(GET_PRODUCT_LIST, {
   fetchPolicy: "network-only" // Doesn't check cache before making a request
 })

We also have the option to specify nextFetchPolicy. When used, fetchPolicy will be used to execute the query for the first time and nextFetchPolicy will be responsible for how the query responds to future cache updates:

 const { loading, error, data } = useQuery(GET_PRODUCT_LIST, {
   fetchPolicy: "network-only",   // Used for first request
   nextFetchPolicy: "cache-first" // Used for next request
 });

Supported fetch policies:

  • cache-first – (DEFAULT) – The Apollo client first executes the cached query. If all the requested data is in the cache, this data is returned. Otherwise, ApolloClient executes the query on the GraphQL server and returns this data after caching it.
  • cache-only – The Apollo client only executes the query in the cache. In this case, it never queries the server. 
  • network-only – The Apollo client executes the full query against the GraphQL server without checking the cache.
  • cache-and-network – Apollo client executes the full query in the cache and on the GraphQL server. The query is automatically updated if the server-side query result modifies cached fields.
  • no-cache – It works the same as with network-only. The difference is that the query result is not cached.
  • standby – Apollo uses the same logic as cache-first. The difference is that the query is not automatically updated when the underlying field values ​​change.

useLazyQuery

This hook works the same as useQuery with the difference that we get to decide when to call a request. In the example below, we can see an added button, which, when clicked, downloads the data.

const ProductListLazy = () => {
 const [getProducts, { data, loading, error }] = useLazyQuery(GET_PRODUCT_LIST)
 
 if (loading) return <span>Loading.</span>
 if (error) return <span>Error.</span>
 
 return (
   <>
     <button onClick={getProducts}>Load products</button>
     <ul>
       {data?.viewer.productList.map(({ productID, name, category }) => (
         <li key={productID}>
           <p>{name}</p>
           {category && (
             <span>
               <strong>category:</strong> {category.name}
             </span>
           )}
         </li>
       ))}
     </ul>
   </>
 )
}
 
export default ProductListLazy

useMutation

Mutations modify data and return a value. They can be used to add, update or delete data.

The useMutation React hook is the main API for performing mutations in an Apollo application. In this case, we’re going to use mutation to create products. 

Since we’re discussing a rather basic example, I’m going to limit myself to adding a product id and name only.

import { gql } from '@apollo/client'
 
export const CREATE_PRODUCT = gql`
 mutation CreateProduct($productId: Float, $name: String) {
   createProduct(record: { productID: $productId, name: $name }) {
     __typename
   }
 }
`

To be able to use the above mutation, we need to create this form:

import { useState } from 'react'
import { useMutation } from '@apollo/client'
import { CREATE_PRODUCT } from './query'
 
const CreateProduct = () => {
 const [name, setName] = useState('')
 const [isProductCreated, setIsProductCreated] = useState(false)
 const [isError, setIsError] = useState(false)
 
 const [createProduct] = useMutation(CREATE_PRODUCT, {
   onCompleted: () => {
     setIsProductCreated(true)
   },
   onError: () => {
     setIsError(true)
   },
 })
 
 const handleSubmit = (e) => {
   e.preventDefault()
 
   createProduct({
     variables: {
       productID: Math.random(),
       name,
     },
   })
 }
 
 const handleClose = () => {
   setIsProductCreated(false)
   setIsError(false)
 }
 
 return (
   <>
     {!isProductCreated && !isError ? (
       <form name="create-product-form" onSubmit={handleSubmit}>
         <div>
           <input
             placeholder="Name"
             onChange={(e) => setName(e.target.value)}
           />
         </div>
         <div>
           <button type="submit" htmlType="submit">
             Submit
           </button>
         </div>
       </form>
     ) : (
       <div>
         {isProductCreated && <p>Success! Product added!</p>}
         {isError && <p>Something went wrong :(.</p>}
         <button onClick={handleClose}>Close</button>
       </div>
     )}
   </>
 )
}
 
export default CreateProduct

As you can see we used the onComplete and onError options here which make it much easier when using mutations. Find out more about the options here: link

After adding the product, refresh the list of products and if the product has been added correctly – it should be displayed on our list. In this case, it is worth using the component in which we used LazyQuery because it is enough to click the button to refresh the list.

useSubscription

In addition to query and mutation, GraphQL supports a third type of operation – subscriptions.

Subscriptions are used to send data from the server to clients who choose to listen to messages in real-time. The subscription behaviour is similar to queries. The difference is that instead of immediately returning a single response, the result is sent along with a specific event on the server

More information with examples can be found in the official Apollo documentation.

Pagination

Now that you are familiar with ApolloClient Hooks and we have displayed a list of products using the ProductList component – I think it would be a good moment for learning the rules of creating pagination.

There are several paging strategies that the server can use for a specific list box:

  • offset based
  • cursor-based
  • page-number based
  • forward
  • backward

Each of the strategies mentioned above has its application and there is no best strategy.

We’ll use – page-number based – which is the most basic way.

In our component ProductList, the list of products is very long. To increase the User Experience, it would be useful to limit it to a few products displayed at the same time and add pagination in the form of Previous and Next buttons or the Load More button.

Due to the fact that our project is only an introductory role to ApolloClient – we will not delve into creating complicated pagination. Instead, we’re going to create a simple Prev/Next pagination.

In order not to edit the components we have created – create a new ProductListWithPagination component based on the ProductList component. Create a new directory containing the same index.js and query.js as the ProductList. 

  • components/ProductListWithPagination/index.js
  • components/ProductListWithPagination/query.js

For pagination to work, we need to start by editing our query.

According to the API documentation – https://graphql-compose.herokuapp.com/northwind/ – we should change the query to productPagination and get the items array which contains the same product list that we used for query productList. Make changes and check for yourself – the new query will return the same list.

export const GET_PRODUCTS = gql`
 query GetProductList {
   viewer {
     productPagination {
       items {
         productID
         name
         unitPrice
         category {
           categoryID
           name
         }
       }
     }
   }
 }
`;

Ok – we fetched the list and what next? Something is still missing from the new query. According to the documentation – we can add parameters such as page and perPage to query productPagination

  • page – the currently displayed page
  • perPage – the number of displayed elements per page (we will limit ourselves to a maximum of 10 displayable elements)
export const GET_PRODUCTS = gql`
 query GetProductList($page: Int) {
   viewer {
     productPagination(page: $page, perPage: 10) {
       items {
         productID
         name
         unitPrice
         category {
           categoryID
           name
         }
       }
     }
   }
 }
`

You are probably wondering what  $page is – it’s a variable that we will use in our code to be able to dynamically change the current pagination page:

import { useState } from "react";
import { useQuery } from "@apollo/client";
import { GET_PRODUCTS } from "./query";
 
const ProductListWithPagination = () => {
 const PAGE_NUM = 1;
 const [page, setPage] = useState(PAGE_NUM);
 
 const { loading, error, data } = useQuery(GET_PRODUCTS, {
   variables: { page: page }
 });
 
 if (error) return <span>Error.</span>;
 
 const loadNext = () => setPage((prev) => prev + PAGE_NUM);
 const loadPrev = () => setPage((prev) => (page > 1 ? prev - PAGE_NUM : page));
 
 return (
   <>
     <p>
       Total visible items: <strong>{page}</strong>
     </p>
     <button style={{ opacity: page === 1 && "0.5" }} onClick={loadPrev}>
       PREV
     </button>
     <button onClick={loadNext}>NEXT</button>
     <ul>
       {loading && "LOADING..."}
       {data?.viewer.productPagination.items.map(
         ({ productID, name, category }) => (
           <li className="product-list__item" key={productID}>
             <p>
               <strong>name:</strong> {name}
             </p>
             {category && (
               <span>
                 <strong>category:</strong> {category.name}
               </span>
             )}
           </li>
         )
       )}
     </ul>
   </>
 );
};
 
export default ProductListWithPagination;

As a bonus, thanks to the cache, we’re not fetching the same pages many times over.

This is perhaps the simplest pagination we can create. The ApolloClient allows you to create more advanced pagination – as standard I refer to the Apollo documentation.

Summary

As you can see ApolloClient is a great tool for working with GraphQL. I hope that I was able to present the basics thanks to which you will be able to create your own application using ApolloClient. If you are interested in ApolloClient – please refer to the official documentation in which there are more examples – including more advanced ones. You might also want to check the link to the codesandbox project containing the hooks described above.

Michał Moroz

Joining Pagepro in 2018, he excel in front-end development, showcasing his proficiency and passion for creating engaging, user-centric web applications. In the beginning of 2022, Michał transitioned to the role of a React Native Developer at Pagepro, marking a significant shift in his career path. This shift is driven by a desire to leverage his front-end expertise in a more dynamic and rapidly evolving environment, focusing on building cross-platform mobile applications.

Article link copied

Close button