Apollo Client with GraphQL integration into a React Application

2022.05.27

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Introduction

The blog’s agenda is to give a brief explanation of GraphQL and Apollo Client with its setup and integration into a React project.

GraphQL

Generally, an application design includes API endpoints for querying which comes with the drawback of over-fetching or under-fetching the required data. The impact is more significant for larger enterprises with billions and millions of requests. The cost for the server just gets expensive because the amount of data payload is being queried and mutated over a second. In this scenario instead of multiple URLs, GraphQL is an API query language that has a single point entry capable of retrieving selective information. For more information on GraphQL here.

Apollo Client

Apollo Client is a state management library that enables easy local and remote data management with GraphQL. Apollo Client's intelligent caching and declarative approach to data fetching can help you iterate faster while writing less code. Additionally, you can create your dream client by creating extensions over the Apollo Client if you need customization. Some interesting features of Apollo Client include:

Declarative data fetching

Apollo Clients can handle the request cycle from start to end, including loading and error states. You need not worry about adding the middleware or transforming or caching responses. All you need to do is describe the data that your component needs and let Apollo do the heaving lifting.

Combining local and remote data

Apollo Client uses Local State Management features enabling you to use the cache as the single source of truth for your application’s data. By using Apollo Client you can combine local fields and remotely fetched fields in the same query.

Zero-config Caching

One of the key features that sets Apollo Client apart from other data management solutions is its local, in-memory cache. Caching a graph was never an easy task. The only way was to normalize and maintain consistent data across the multiple components in an application. But Apollo Client helps in these scenarios by enabling in-memory-cache, this avoids unnecessary fetching which results in improved performance.

Vibrant Ecosystem

To build out more advanced features, Apollo Client comes with custom functionality i.e. Apollo Link's architecture that supports @apollo/client to create one's dream client by building an extension on top of Apollo Client. Some of the community's extensions include: apollo3-cache-persist, apollo-storybook-decorator, AppSync by AWS, etc.

Implementation of Apollo Client

Prerequisite

Create a new React project locally with create react app.

Install Apollo Client Package

Install the package required for Apollo Client and GraphQL.

npm install @apollo/client graphql

Initialization of Apollo Client

Let's initialize Apollo Client by initializing its constructor the below values which are URI and cache. For example, I'm using the rickandmortyapi endpoint, and the URL is https://rickandmortyapi.com/graphql.

const client = new ApolloClient({
uri: 'https://rickandmortyapi.com/graphql',
cache: new InMemoryCache()
});

URI specifies the GraphQL server. Cache is an instance of InMemoryCache. On querying, Apollo Client uses InMemoryCache to cache the results. On any new request, we first check for a hit in the cache, else we proceed as a new request to the endpoint.

Connect apolloClient to React

We can connect Apollo Client to React by wrapping the root component with ApolloProvider.

<ApolloProvider client={client}>
  <App />
</ApolloProvider>

Simple Query Template for Fetching

Create a query named GET_ALL_CHAR and wrap the query string in the gql function. gql is a tag from @apollo/client, used to write and parse a GraphQL query into a standard GraphQL AST. Query string includes the actual GraphQL query. We start with query and continue the query structure matching the schema. Here's a simple example, for more details on syntax read further.

const GET_ALL_CHAR = gql`
query GetAllCharacters{
 characters {
   results {
     id
     name
     image
   }
 }
}
`;

Query Execution

Apollo Client uses a React hook called useQuery to bind a query to your component. This enables the component to render a query’s result immediately. The hook useQuery encapsulates the logic for retrieving data, tracking the request, loading, and error handling. This helps your component to bind to query and update the component.

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

Implementation

Here are some important code snippets. For complete source code here.

src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
} from '@apollo/client';
import { BrowserRouter } from 'react-router-dom';
const client = new ApolloClient({
uri: 'https://rickandmortyapi.com/graphql',
cache: new InMemoryCache(),
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
 <BrowserRouter>
   <ApolloProvider client={client}>
     <App />
   </ApolloProvider>
 </BrowserRouter>
</React.StrictMode>
);

src/App.js

import CharacterList from './pages/CharacterList';
import Character from './pages/Character';
import { Route, Routes } from 'react-router';
function App() {
return (
 <div className='App'>
   <Routes>
     <Route strict exact path='/' element={<CharacterList />} />
     <Route strict exact path='/:id' element={<Character />} />
   </Routes>
 </div>
);
}
export default App;

src/pages/CharacterList.js

import React from 'react';
import './CharacterList.css';
import { useCharactersDisplay } from '../hooks/useCharactersDisplay';
import { Link } from 'react-router-dom';
export default function CharacterList() {
const { error, data, loading } = useCharactersDisplay();
if (loading) return <div>...loading....</div>
if( error) return <div>.....error</div>
return <div>
 {data.characters.results.map(character => {
   return (

       <br />
       <img src="{character.image}" alt='character' />
       <div>{character.name}</div>
       <br />

   );
 } )}
</div>;
}

src/hooks/useCharactersDisplay

import { useQuery, gql } from '@apollo/client';
const GET_ALL_CHAR = gql`
query GetAllCharacters{
 characters {
   results {
     id
     name
     image
   }
 }
}
`;
export const useCharactersDisplay = props => {
const { error, data, loading } = useQuery(GET_ALL_CHAR);
return {error, data, loading};
};

How to write a Query String

As a fresher to GraphQL and Apollo Client, drafting a query might seem slightly challenging. I would like to share how to write one.

Understanding Schema

Let's consider an example server endpoint "https://rickandmortyapi.com/graphql". To write a query, it's most important to understand the schema. schema In the above schema, we can see the object types and fields on those types. For now, let's focus on type Character and type Characters. These are object types with their fields. Fields as shown in the image describe their type as pointing to a primary data type as String, ID, Int or even to object. For example, the field results in Characters tells its type Character, and enclosures with '[]' indicate that it returns a list of character objects. type Query is a special object type that defines top-level entry points for querying. Here let's consider the Query endpoint: character(id: ID!): Character. character accepts an argument of id and returns a Character object.! indicates that the argument is mandatory.

Query without Arguments

While writing the query, please refer to the field structure in the docs. To get the query right, it is important to match the shape of the object types in the schema. docs

  • Below is a simple query requesting names and images from characters.
    • Line 1, Rather than writing the whole query in the useQuery hook, we can do so by assigning the query to the variable.
    • enclosing with gql from @apollo/client is mandatory as this notifies the compiler to parse as a query.
    • Line 2, For querying, we start with a query keyword followed by a name (which can be anonymously when there is no argument sent) and open the body with {...}.
    • Line 3, In the query string we start with the root type name, here it is characters then proceed to results.
    • Line 4, results is a field pointing to an object list, hence I must specify the required field in that object list, where I choose it to be name and image.
    const GET_ALL_CHAR = gql`
      query GetAllCharacters {
        characters {
          results {
            name
            image
          }
        }
      }
    `;

    Results from GraphQL are in JSON format.

    Query with Arguments

    Now let's consider the character endpoint, which has ID as a mandatory argument.

  • with the same rules as above, we add an argument. Here's the example query:
  • const GET_CHAR = gql`
    query GetCharacter($id: ID!) {
     character(id: $id) {
       name
       gender
       image
        episode {
         name
         air_date
       }
     }
    }
    `;

    In association with an argument in the schema, while executing the query we shall pass the argument as variables.

    export const useCharacter = (id) => {
    const { error, data, loading } = useQuery(GET_CHAR, {
       variables:{
           id
       }
    });
    return { error, data, loading };
    }

    References

    • https://www.apollographql.com/docs/react/
    • https://graphql.org/learn/
    • https://www.youtube.com/watch?v=gAbIQx26wSI