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/.
app-preview
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: