How to use GraphQL on the Client-side with React and TypeScript

Ibrahima Ndaw

JavaScript enthusiast, Full-stack developer⚛️ & blogger📝

In this tutorial, we will be looking at using GraphQL on the client-side with React, TypeScript, and Apollo Client. This article is a follow-up of How to use TypeScript with GraphQL (server-side) in which we built a GraphQL API using TypeScript and TypeGraphQL. We’ll be using the API we created in that article, so if you haven’t already, you may want to catch up before diving into the client-side because. Let's get started!

Prerequisites

This guide assumes that you have basic experience with React and TypeScript. You will learn how to use GraphQL in a React App to interact with a GraphQL API and then retrieve the data using Apollo Client. We’ll be building a Todo App that relies on the API to add and fetch the Todos.
You can preview the GraphQL API in this CodeSandbox

Setting up

To start a new React App, execute this command on the command-line-interface (CLI):

npx create-react-app react-typescript-graphql

Next, we have to install the Apollo and GraphQL libraries. The Apollo Client will allow us to communicate with a GraphQL API. Open the React App directory in your CLI and run the following command:

yarn add apollo-boost @apollo/react-hooks graphql

Or for npm

npm install apollo-boost @apollo/react-hooks graphql

Now let's structure the project as follows:

src
| ├── components
| |  ├── AddTodo.tsx
| |  └── Todo.tsx
| ├── type
| |  └── Todo.ts
| ├── App.tsx
| ├── useRequest.ts
| ├── graphql.ts
| ├── index.ts
| └── index.css

There are two files to take special notice of:

  • useRequest.ts is a custom hook that helps fetch data using Apollo Client.
  • graphql.ts holds the GraphQL logic to interact with the API.

With this folder structure in place, we can get our hands dirty and create our TypeScript Types!

Creating the TypeScript Types

types/Todo.ts

export interface ITodo {
  id?: string;
  title: string;
  description: string;
}

export interface ITodos {
  getTodos: ITodo[];
}

export type ITodoMutation = {
  addTodo: ITodo;
};

Let’s explore what each type describes. The  ITodo type describes the shape of a Todo. We use the ITodo type to create ITodos which returns an array of Todos from the API. Finally, we rely on ITodo to define the type expected by the GraphQL mutation query ITodoMutation.

Next we’ll add Apollo Client into our React App.

Connecting React to Apollo Client

index.ts

import * as React from "react";
import { render } from "react-dom";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "@apollo/react-hooks";

import App from "./App";

const client = new ApolloClient({
  uri: "https://tyoku.sse.codesandbox.io/graphql"
});

const rootElement = document.getElementById("root");
render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  rootElement
);

After importing ApolloClient, we create a new instance of it and pass in the URL of the GraphQL API.  To connect it with React, we need to pass the client object to the ApolloProvider component. Apollo client can now be used to retrieve data from the API.

Next we’ll use gql and the hooks provided by Apollo Client to send the GraphQL queries to the API.

Writing the GraphQL queries

graphql.ts

import gql from "graphql-tag";

export const GET_TODOS = gql`
  {
    getTodos {
      id
      title
      description
      status
    }
  }
`;

export const ADD_TODO = gql`
  mutation AddTodo($title: String!, $description: String!) {
    addTodo(todoInput: { title: $title, description: $description }) {
      id
      title
      description
      status
    }
  }
`;

As you can see, GET_TODOS is a GraphQL Query for retrieving all Todos from the API and a GraphQL Mutation ADD_TODO for adding a new Todo. The mutation query expects a title, and a description in order to create a new Todo on the backend.

Fetching the Todos from the GraphQL API

useRequest.ts

import { DocumentNode, useQuery, useMutation } from "@apollo/react-hooks";
import { ITodos, ITodoMutation } from "./types/Todo";

export function useTodoQuery(gqlQuery: DocumentNode) {
  const { loading, error, data } = useQuery<ITodos>(gqlQuery);
  return { loading, error, data };
}

export function useTodoMutation(gqlQuery: DocumentNode) {
  const [addTodo] = useMutation<ITodoMutation>(gqlQuery);
  return [addTodo];
}

This custom hook is optional. You can skip it and use the Apollo hooks directly in your components.

In this file, we first have a function useTodoQuery that expects a GraphQL Query to fetch all Todos from the API and then returns the data. Next, we use the useTodoMutation method to create a new Todo based on the data received as a parameter.

So far, we have connected React and Apollo and created the GraphQL queries to access the API. Next, let’s build the React components to that will consume the returned data.

Creating the components

components/Todo.ts

import * as React from "react";
import { ITodo } from "../types/Todo";

type Props = {
  todo: ITodo;
};

const Todo: React.FC<Props> = ({ todo }) => {
  const { title, description } = todo;

  return (
    <div className="Card">
      <h1>{title}</h1>
      <span>{description}</span>
    </div>
  );
};

export default Todo;

The Todo component is responsible for the display of a Todo object. It receives the data of type Itodo and then uses destructuring (JavaScript expression for unpacking values from arrays or objects into distinct variables.) to pull out the title and the description of the Todo.

components/AddTodo.ts

import * as React from "react";
import { ApolloCache } from "@apollo/react-hooks";
import { FetchResult } from "apollo-boost";

import { useTodoMutation } from "../useRequest";
import { ADD_TODO, GET_TODOS } from "../graphql";
import { ITodo, ITodoMutation, ITodos } from "../types/Todo";

const AddTodo: React.FC = () => {
  const [formData, setFormData] = React.useState<ITodo | {}>();
  const [addTodo] = useTodoMutation(ADD_TODO);

  const handleForm = (e: React.FormEvent<HTMLInputElement>) => {
    setFormData({
      ...formData,
      [e.currentTarget.id]: e.currentTarget.value
    });
  };

  const handleSaveTodo = (
    e: React.FormEvent,
    { title, description }: ITodo | any
  ) => {
    e.preventDefault();
    addTodo({
      variables: { title, description },
      update: (
        cache: ApolloCache<ITodoMutation>,
        { data: { addTodo } }: FetchResult<ITodoMutation>
      ) => {
        const cacheData = cache.readQuery({ query: GET_TODOS }) as ITodos;
        cache.writeQuery({
          query: GET_TODOS,
          data: {
            getTodos: [...cacheData.getTodos, addTodo]
          }
        });
      }
    });
  };

  return (
    <form className="Form" onSubmit={(e) => handleSaveTodo(e, formData)}>
      <div>
        <div>
          <label htmlFor="name">Title</label>
          <input onChange={handleForm} type="text" id="title" />
        </div>
        <div>
          <label htmlFor="description">Description</label>
          <input onChange={handleForm} type="text" id="description" />
        </div>
      </div>
      <button>Add Todo</button>
    </form>
  );
};

export default AddTodo;

After importing the useTodoMutation hook into our component, we pass in the GraphQL mutation query ADD_TODO as an argument. Next, we handle the data entered by the user with the handleForm function and useState. Once the user submits the form, we call the addTodo method to create the Todo with the mutation query. To preview the Todo created, we need to update the Apollo cache by spreading the old Todos with the new one in an array of Todos.

We are now able to create and display a list of Todos. Finally, let's put it all together and use the components in the App.ts file.

Showing the Todos

App.ts

import * as React from "react";
import "./styles.css";

import { GET_TODOS } from "./graphql";
import { useTodoQuery } from "./useRequest";
import AddTodo from "./components/AddTodo";
import Todo from "./components/Todo";
import { ITodo } from "./types/Todo";

const App: React.FC = () => {
  const { loading, error, data } = useTodoQuery(GET_TODOS);

  if (loading) return <h1>Loading...</h1>;
  if (error) return <h1>Something went wrong!</h1>;

  return (
    <div className="App">
      <h1>My Todos</h1>
      <AddTodo />
      {data.getTodos.map((todo: ITodo) => (
        <Todo key={todo.id} todo={todo} />
      ))}
    </div>
  );
};

export default App;

In this App component, we use the `useTodoQuery` hook to retrieve all Todos from the GraphQL API.

Next, we loop through the response data and display it using the Todo component.

With this step, the app is ready to be tested on the browser. Open the project directory in the CLI and run this command:

yarn start
## or
npm start

If everything works as expected, you should be able to see the React app here: http://localhost:3000/.

And that's it! Our React app is looking good!

We’ve built a Todo App with React, TypeScript, GraphQL, and Apollo Client. You can preview the finished project in this CodeSandbox.

Conclusion

In this tutorial, we learned how to use GraphQL on the client-side with React, TypeScript, and Apollo Client. We also used the GraphQL API built with TypeGraphQL as a backend to finish up with a full-stack strongly-typed app. A very exciting stack to try on your next project!

Resources

Check out these resources to dive deeper into the content of this tutorial: