Setting Up React With GraphQL

Published on October 26, 2022

GraphQL Client

In this article, we created a simple GraphQL server using Apollo Server. Although dealing with Apollo server and querying it directly is fun, Apollo really shines when paired with a view layer like React. While I have experience with other GraphQL Clients such as urql, I must confess Apollo Client, which is primarily a state management library, remains my favorite.

Getting Started 🚀

Create a new project from scratch:

$ npx create-react-app library-app

Inside the app, install the required apollo and graphQL dependencies:

$ npm i @apollo/client graphql

@apollo/client is a package that contains all that is needed to set up Apollo Client while graphql is a package that helps with parsing graphQL queries into valid GraphQL documents.

Initialize GraphQL Client

With the dependencies in place, we can initialize an instance of the Apollo client. Start by importing the necessary symbols from the @apolloclient package.

src/index.js
// ...
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache, gql } from "@apollo/client"

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: new HttpLink({
    uri: "http://localhost:4000",
  }),
})

const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
    <ApolloProvider client={client}>
    <App />
    </ApolloProvider>
)

We create client object, which is an instance of ApolloClient. The client receives two arguments:

    1. cache is an instance of InMemoryCache and is used by Apollo Client to cache results after fetching them,
    1. link is an instance of HttpLink. It connects GraphQL to our server. The uri parameter receives the url of the server

To complete the setup, we wrap our React app with the ApolloProvider component, also imported from @apollo/client. This will avail the client instance to the entire application. The ApolloProvider component leverages React’s Context API under the hood to avail the client instance to the entire React component tree.

Make GraphQL queries

To write GraphQL queries, we use the gql function from the graphql package. The query is written as a template string as shown below:

src/queries.js
import { gql } from "@apollo/client";

export const ALL_AUTHORS = gql`
  query {
    authorList {
      id
      name
      age
    }
  }
`;

export const GET_BOOKS = gql`
  query {
    bookList {
      id  
      title
      author {
        name
        age
      }
      published
    }
  }
`;

When executed, the two queries will fetch a list of books and their authors from the GraphQL server:

import { GET_BOOKS } from './queries';

client.query({ query: GET_BOOKS }).then((res) => {
  console.log(res);
});

This will log the fetched books data.

Fetching Data Using GraphQL hooks

Apollo Client provides a great hooks API for fetching and mutating data. To retrieve data from the server, we use the useQuery hook:

import { useQuery } from "@apollo/client";
import { GET_BOOKS } from './queries';

function App() {
  const { data } = useQuery(GET_BOOKS)
  // ...
}

Import and call the useQuery hook, passing in your defined query as an argument. This will return an object with a data property that contains the result of the query. Other properties contained in the result object are loading and error, which you can use to determine what to render.

GraphQL Variables

To make the app more dynamic and allow user input, GrapQL provides a way to pass variables. For example, let’s say we want to add a search feature to our library app that allows users to search for books by title. We could write a GraphQL query that accepts a author variable to filter the results:

src/queries.js
// ...
export const SEARCH_BOOKS = gql`
  query findBooksByAuthor($author: String!) {
    findBooksByAuthor(where: { author: $author }) {
      title
      published
    }
  }
`

We name our query findBooksByAuthor and declare the variable(s) we want to use for the query and their types. The syntax is $variableName: type. To pass in the author variable when executing the query, you can use the useQuery hook and pass in an options object with a variables property:

// ...
function SearchForm({ author }) {
  const { data, loading } = useQuery(SEARCH_BOOKS, {
    variables: { author }
  });
  if (loading) {
    return <p>Loading...</p>;
  }
  if (data) {
    return (
      <ul>
        {data.bookList.map(book => (
          <li key={book.title}>{book.title} by {book.author}</li>
        ))}
      </ul>
    );
  }
  return null;
}

Here, we are passing the author value as a prop to the SearchForm component, and passing it as a variable to the useQuery hook.

useLazyQuery

When React renders a component that has useQuery, Apollo Client automatically executes that query. But sometimes, you will want to execute a query only in response to user events e.g. when a button is clicked.

useLazyQuery is perfect for running event-based queries for example the search or filtering functionality. Unlike useQuery, useLazyQuery does not execute its associated query immediately. Instead, useLazyQuery returns a query function in its results that you can call in response to an event.

Let’s say we have a form that allows users to search books by author:

import { useLazyQuery } from 'react-apollo';
import { SEARCH_BOOKS } from './queries';

function SearchForm({ author }) {
  const [searchBooks, { data, loading }] = useLazyQuery(SEARCH_BOOKS);

  const handleSubmit = e => {
    e.preventDefault();
    searchBooks({ variables: { author } });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" value={author} onChange={e => setauthor(e.target.value)} />
      <button type="submit">Search</button>
      {loading && <p>Loading...</p>}
      {data && (
        <ul>
          {data.books.map(book => (
            <li key={book.title}>{book.title} by {book.author}</li>
          ))}
        </ul>
      )}
    </form>
  );
}

When the form is submitted, the handleSubmit function is called, which calls the searchBooks function with the author variable. This manually triggers the execution of the GraphQL query, and the data and loading variables are updated with the results of the query.

Alternatively, we can also stick with useQuery and use its skip field that enables the query to run only if a certain condition is met. This prevents the query from being executed automatically on component render:

function SearchForm({ author }) {
  const { data, loading } = useQuery(SEARCH_BOOKS, {
    variables: { author },
    skip: !author
  });

  const handleSubmit = e => {
    e.preventDefault();
    // No need to manually execute the query here, as it will be automatically
    // executed when the author value changes
  };
  // ...
}

We set the value of skip to the negation of author. This will cause the query to be skipped (and not executed) if author is falsy (e.g. an empty string) and will cause the query to be executed if author has a truthy value (e.g. a non-empty string).

Apollo Cache

How To Update Data After a Mutation

After running a mutation, the next natural thing would be to update your UI inorder to reflect the mutated data. There are a couple of ways to achive this:

Refetch Queries

Update Apollo Cache

Conclusion

In this article, we learned how to set up a simple library app with React, GraphQL, and Apollo. We installed the necessary dependencies, set up the Apollo client, and wrote a GraphQL query to fetch a list of books from a server.