How to integrate Apollo client with React

If you have some experience working with GraphQL servers and GraphQL query language, you must have used GraphQL playgrounds on localhost or online. On these playgrounds, you can test your GraphQL queries and mutations against the GraphQL server to fetch data and make changes. You can also use variables to make these queries and mutations dynamic. So, the GraphQL playground is running on the client side as a testing tool for developers.

When it comes to the real world, you can not ask your clients to run queries and mutations to fetch or add new data. And this is where you need a GraphQL Client program. Therefore, in this post, we are going to discuss how to use Apollo client as a GraphQL client with React applications.

If you stumble upon this post with zero understanding of GraphQL, first, read the following posts, and then come back to this one.

But, if you want to have an in-depth understanding of GraphQL I would highly recommend you to follow this series in GraphQL.

What is Apollo client?

There are many Graphql clients on the market with different levels of complexity. But, we will focus on how to use the Apollo client to fetch data from the backend server that supports GraphQL.

As a reminder, a GraphQL client is a program that you can use to send queries and mutations to the GraphQL server. You can do this with just HTML and vanilla javascript without using any GraphQL client. But. that can be cumbersome as your code grows. Therefore, the best way is to use a GraphQL client such as Apollo client.

We are not going to create the backend GraphQL server here. Therefore, I am using https://graphqlzero.almansi.me/api as a mock GraphQL server. Please look at their documentation to get familiarized with the queries and mutations that you can run. The documentation is pretty easy to understand if you have basic knowledge of GraphQL.

Setting up React with Apollo client

First, we need to create a React project. I am using Vite.js for this.

Run npm create vite@latest on your terminal and follow the prompts:

Project name:  graphql-react
framework: React 
variant: Javascript

It will create a folder of project files with vite.js configuration. After that.

cd graphql-react // go into the project file
npm install @apollo/client graphql

//If you prefer, you can run this command later, after we added some code
npm run dev

Now, open your root project folder in a text editor.

First thing first, we got to do some cleaning up.

  • Remove all the files inside the \src folder except main.jsx and App.jsx.

  • Now copy and paste the following code to main.jsx

import React from 'react';
import ReactDOM from 'react-dom/client'
import { ApolloProvider } from '@apollo/client';
import App from './App';
import client from './api/apollo'

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>,
  </React.StrictMode>
);

As you can see in the above code, I have imported apollo.js from ./api/apollo.

We still do not have this file. This is where we connect our Apollo client with the Backend Apollo GraphQL server( API).

  • Create a folder called api in the src folder

  • Now create a file named apollo.js in the api folder

  • Copy and paste the following code to apollo.js

import { ApolloClient, InMemoryCache  } from '@apollo/client';

const client = new ApolloClient({
    uri: 'https://graphqlzero.almansi.me/api',//link to our fake server
    cache: new InMemoryCache(),
  });

export default client

Apollo client uses a caching mechanism in fetching data from the backend server. When you send a request to the backend, the Apollo client will try to fetch data from the in-memory cache. If it does not find that data, your query will be sent to the backend server, fetch the data, store fetched data in an in-memory cache, and send the response to the client.

If you request the same data in subsequent requests, the Apollo client will fetch the data from the cache and not from the backend API. This improves the speed and performance of your application.

This is explained clearly in this diagram at Apollo client docs in their overview of caching

How to Run GraphQL queries in Apollo client

Testing the queries on GraphQL playground

As we are using the fake API at https://graphqlzero.almansi.me/api, we are going to use their playground to test our queries and use those queries in our application.

First visit https://graphqlzero.almansi.me/api

We are going to fetch three posts from this API. The query you should run is:

query {
  posts(options:{paginate : { limit: 3 }}){
    data{
      id
      title
    }
  }
}

You can learn about their queries and mutation by referring to their docs at https://graphqlzero.almansi.me/

How to add GraphQL queries to your app

How are we going to run the above query in our React application?

  • In the src folder, create another folder called graphql

  • After that, create a sub-folder named posts inside graphql folder

  • In this posts folder, create a file and name it as queries.js

  • Now copy and paste the following code

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

export const GET_ALL_POSTS = gql`
query {
    posts(options:{paginate : { limit: 3 }}){
      data{
        id
        title
      }
    }
  }
`;

As you can see in the above code we have imported gql function( in more specific terms it is a template literal tag ) from ‘@apollo/client’. We are passing the queries to fetch data as a parameter of this function. If you are confused about this syntax, have a quick look at the Tagged templates on MDN. We are going to use this query in another React component. Therefore, we need to export it.

Running GraphQL Queries with useQuery hook in Apollo Client

Apple client introduces the useQuery hook to use GraphQL queries to fetch data from the backend Graphql server.

The basic usage ( syntax ) : const response = useQuery( query, options );

query: Represents the GraphQL query that you want to execute. It defines the data fetching operation you want to perform in your GraphQL API.

options (optional): Provides additional options and configurations for the query, such as variables, context, caching behavior, and polling. It allows you to customize the behavior of the query operation.

What does the useQuery hook return?

useQuery returns an object with many properties. Therefore we can apply destructing assignment.

const { loading, error, data } = useQuery( query, options );

data: this object contains the fetched data of your GraphQL query after it completes.

error: if your query result in either graphQL errors or network errors, you can access those using this object.

loading: this property can be utilized to show the network status of your request.
A complete reference of useQuery can be accessed at Apollo API reference for hooks.

How to use useQuery hook

Let’s see how to use useQuery in our code.

  1. Create a file named getAllPosts.jsx in src/graphql/posts.

  2. Copy and paste the following code ( Note: This is not the complete code, but it is enough to understand concepts. You can download the full code from the resources section )

import React from 'react'
import { useQuery  } from '@apollo/client';
import { GET_ALL_POSTS } from './queries';

export default function getAllPosts() {
    const { loading, error, data } = useQuery( GET_ALL_POSTS );

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

    return (
        <div>
          { data.posts.data.map( post => <li key={ post.id }>{ post.title  }</li>) }  
        </div>
    )}

As you can notice in the above code, the userQuery is imported from '@apollo/client. In addition to that, you need to import the GraphQL query to be consumed in this query, and that is why we have the statement import { GET_ALL_POSTS } from './queries'.

To run your query, you need to pass the GET_ALL_POSTS query to the useQuery hook.

Now, replace any code in App.jsx with the code below. Here we simply import the getAllPosts component to App.jx.

import GetAllPosts from './graphql/posts/getAllPosts';

export default function App() {

  return (
    <div>
      <h2>All posts</h2>
      <GetAllPosts />
    </div>
  )}

Now, you should be able to view the titles of three posts on your browser. ( if you did not run your app before, you can now run “npm run dev” on the terminal to run the app )

How to use useQuery with variables

If you look at our query in queries.js, we are limited to getting three posts by adding 3 to the limit, which is an argument.

But what if you can specify any number for this limit argument so that you can have more control in fetching posts?

For this, you need to change the query to add variables as below.

query($limit: Int ){
    posts(options:{paginate : { limit: $limit }}){
      data{
        id
        title
      }
    }
  }

The JSON object that you would use for testing this query would be

{
  "limit": 3
}

Now, you can replace the query we already have in src/qrapql/queries.js with the query above.

Then, you also need to modify the useQuery hook. Remember, we need to pass that JSON object to run the query.

useQuery has the variables option for this. Let’s use it and modify the code.

const { loading, error, data } = useQuery( GET_ALL_POSTS ,{ variables: { limit: 3 }} );

In the above code, the value 3 is hard coded. But, you can use a variable to replace it

const { loading, error, data } = useQuery( GET_ALL_POSTS , { variables: { limit: postLimit  }});

Manual query execution with useLazyQuery hook

useQuery hook executes automatically when React component renders on the browser. However, if you want to dynamically accept data and execute a query on a user event, you need to use useLazyQuery hook.

Take a look at the following code:

const [ getPost,  { loading, error, data }  ] = useLazyQuery( GET_POST );

GET_POST: the query to get a specific post. It searches by the Id of the post

src/qraphql/queries.js

export const GET_POST = gql`
    query( $id: ID!){
      post(id: $id) {
        id
        title
        body
      }
    }`;

To excuse this query, you must call the getPost function by passing the variables option.

data.posts.data.map( post => <li key={ post.id }><a href='#'
                  onClick={ ()=>getPost ( { variables: { id : post.id }}) }>{ post.title  }</a>
              </li>)

How to execute Create, Update, and Delete operations in Apollo client

Testing the mutation on the GraphQL playground

In GraphQL mutations are used to make changes in the GraphQL server. Before you add any mutations to your application, you need to test your mutations.

As with queries, use the API at https://graphqlzero.almansi.me/api, to test the following mutations. See what this mutation returns and have a close look at the variables that hold input values.

First visit https://graphqlzero.almansi.me/api

To create a post:

mutation ($input: CreatePostInput!) {
            createPost(input: $input) {
                id
                title
                body
            }
        }

Variables:

{
  "input": {
    "title": "Test Post",
    "body": "This is a test post"
  }
}

To update a post:

mutation (
    $id: ID!,
    $input: UpdatePostInput!
  ) {
    updatePost(id: $id, input: $input) {
      id    
      title
      body
    }
  }`

Variables:

{
  "id": 2,
  "input": { 
    "title": "Test Update Post",
    "body": "Some updated content."
  }
}

To delete a post:

mutation ( $id: ID! ) {
        deletePost(id: $id)
    }

Variables:

{
  "id": 1
}

These mutations can be found in the docs at https://graphqlzero.almansi.me/. You can customize input fields and output fields as per your requirements.

Integrating the mutation into your app

How are we going to run the above query in our React application?

  • In the src/qraphql/posts folder create another file named mutations.js

  • Add all you following code to this file

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

export const CREATE_POST =  gql`
    mutation ($input: CreatePostInput!) {
         createPost(input: $input) {
            id
            title
            body
     }}`;

export const DELETE_POST = gql`
    mutation ( $id: ID! ) {
       deletePost(id: $id)
    }`;

export const UPDATE_POST = gql`
   mutation ( $id: ID!, $input: UpdatePostInput! ) {
       updatePost(id: $id, input: $input) {
          id    
          title
          body
    }}`;

Create, Update, and Delete records with the UseMutation hook

UseQuey is only for fetching data. In other words, it is for READ operation in a CRUD application.

Apollo Client provides another hook called the useMutation for utilizing GraphQL mutations to make changes in the backend GraphQL API

One important thing you need to remember is that, unlike the useQuery hook, the useMutation hook DOES NOT call automatically when React component renders on the browser.

This means you always need to invoke it on a user event or any other trigger.

The most basic usage (syntax ): const [ mutateFunction ] = useMutation(MUTATION_NAME);

MUTATION_NAME is the mutation that you need to pass to the useMutation. It is the mutation operation you want to perform in your GraphQL API.

In many circumstances, we need to capture the response returned by the useMutation. The basic syntax for this is

const [mutateFunction, response ] = useMutation(MUTATION_NAME);

Here the response is an object with many properties. Therefore, we can deconstruct it to access the properties

const [addPost, { data, loading, error }] = useMutation( MUTATION_NAME );

data, loading, and error are some of the properties of The object returned useMutation hook. data, loading, and error properties are similar to the useQuery hook. You can learn more about these properties and additional properties in the mutation result in the Apollo documentation on the useMutation API.

How to useMutation hook

When you create, update, or delete backend data, you need to pass data to the backend. You can use the variables option of useMutation hook to pass dynamic data from user input.

Note: we are using a mock API in the backend. Therefore, real create, update, or delete operation does not occur. They are only simulations. But, this is not an issue to understand how the useMutation hook works.

Creating new posts

  • Create a new file named createPost.jsx in src/graphql/posts, and add the code from the following GitHub Gist

createPost.jsx: code for creating a new post

When the user submits the form above, the addPost() function is called. This function takes the variables option, which is an object with the user inputs: title and body.

onSubmit={e => {
            e.preventDefault();

            addPost({
                variables: {
                    input: {
                        title: titleRef.current.value ,
                        body: bodyRef.current.value }
            }});

            titleRef.current.value = '';
            bodyRef.current.value = '';
}}

Updating and deleting a post

When it comes to updating or deleting records how you use useMutation is not that different from how you use useMutation to create new posts. The main difference is that you need to pass the id of the selected post that you decided to update.

You can create two files named updatePost.jsx and deletePost.jsx, and then copy and paste the code for update and delete from these Github Gists.

updatePost.jsx : code for updating a post

deletePost.jsx: code for deleting a post

In updatePost.jsx, the key things that are relevant to mutation is

  • Importing the mutation query and useMutatin hook
import { UPDATE_POST } from './mutations';
import { useMutation  } from '@apollo/client';
  • Passing the mutation query to the useMutation hook
const[ updatePost,{ data, loading, error } ] = useMutation( UPDATE_POST );
  • Calling the updatePost function on a user event. In code, it is when submitting the form.
const handleSubmit = (e)=>{
      e.preventDefault();
      updatePost({
          variables: {
              id: post.id,
              input: {  title , body }
      }});
      setAllState();//reseting states and input fields
  }

Deleting a post

In this demo app, the DeletePost component is responsible for deleting a post. It is a child component of the UpdatePost component.

In deletePost.jsx, the key parts of the code that is responsible for mutation are;

  • Importing the mutation query and useMutatin hook
import {  DELETE_POST } from './mutations';
import { useMutation  } from '@apollo/client';
  • Passing the mutation query to the useMutation hook
const [ deletePost, { data, loading , error } ] = useMutation( DELETE_POST);
  • Calling the deletePost function on a user event. In code, it is when submitting the form.
const handleDelete = ()=>{    
        deletePost( { variables : { id : post.id }})      
        item.current[ post.id ].remove();  
        setAllState('');     
  }
return (
    <div>
        <button onClick={ handleDelete } type="submit">Delete</button>
    </div>
)

Conclusion

When you want to fetch records from the backend data source via the Graphql server, you need to have a Graphql client in the front end. And We explore the Apollo client as a GraphQL client. The useQuery and useMutation hooks are the main hooks that are available in the Apollo client to pass GraphQL queries and mutations to the GraphQL server. In addition, we also the useLazyQuery hook to fetch data on a user event in our application.

In a CRUD application, while you useQuery and useLazyQuery hooks are used to pass Graphql queries to fetch data from the backend in Read operations, the useMutation hook is used to pass mutations for Create, Update, and Delete operations in the backend API or data source

Resources

Download the full code for this post

I also add the following demo app for your reference. This is basically the same app. But, it uses React context. I also change the project structure to organize the code better.

Apollo client with React Context API