See how to get started with GraphQL and the React Query tool in React—a widely adopted library for building user interfaces that couples well with GraphQL APIs.
GraphQL is a query language for making requests to APIs. With GraphQL, the client tells the server exactly what it needs and the server responds with the data that has been requested. In an earlier article, we went through an exercise to create a GraphQL server API with Apollo Server in Node.js.
In today’s article, we’ll spend some time seeing how we can get started with using GraphQL on the client and we’ll use React, a widely adopted library for building user interfaces that couples well with GraphQL.
In the realm of GraphQL, the client serves as the medium through which we interact with our GraphQL server. In addition to sending queries/mutations and receiving responses from the server, the client is also responsible for managing the cache, optimizing requests and updating the UI.
Though we can make a GraphQL HTTP request with a simple POST command, using a specialized GraphQL client library can make the development experience much easier by providing features and optimizations like caching, data synchronization, error handling and more.
Many popular GraphQL clients exist in the developer ecosystem today such as Apollo Client, URQL, Relay and React Query. In this article, we’ll leverage React Query as our GraphQL client library.
Assuming we have a running React application, we can begin by installing the @tanstack/react-query
, graphql-request
and the graphql
packages.
npm install @tanstack/react-query graphql-request graphql
@tanstack/react-query
is the React Query library we’ll use to make queries and mutations. The graphql-request
and graphql
libraries will allow us to make our request functions to our GraphQL server and provide the necessary utilities to parse our GraphQL queries.
To begin using React Query utilities within our app, we’ll need to first set up a QueryClient
and wrap our application’s root component within a QueryClientProvider
. This will enable all the child components of App
to access the QueryClient
instance and, therefore, be able to use React Query’s hooks and functionalities.
In our root index file where the parent <App />
component is being rendered, we’ll import QueryClient
and QueryClientProvider
from the tanstack/react-query
library.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
QueryClient
is responsible for executing queries and managing their results and state, while QueryClientProvider
is a React context provider that allows us to pass the QueryClient
down our component tree.
We’ll then create a new instance of QueryClient
and pass it down as the value of the client
prop of QueryClientProvider
that we’ll wrap the root App
component with.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import App from "./App";
// create the query client instance
const queryClient = new QueryClient();
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
// pass our query client down our app component tree
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
The main composition function, provided to us from React Query, to execute GraphQL queries is the useQuery function.
The useQuery()
hook takes a unique key and an asynchronous function that resolves the data returned from the API or throws an error. To see this in action, we’ll attempt to make a GraphQL query to the publicly accessible Star Wars GraphQL API.
We’ll create a component named App
and utilize the useQuery()
hook within it to retrieve a list of films from the allFilms
query field. First, we’ll construct the GraphQL query as follows:
import { gql } from "graphql-request";
const allFilms = gql/* GraphQL */ `
query allFilms($first: Int!) {
allFilms(first: $first) {
edges {
node {
id
title
}
}
}
}
`;
The GraphQL document above defines a query named allFilms
, which accepts a variable labeled $first
that limits the number of films retrieved from the API.
In the component function, we’ll leverage the useQuery()
hook to initialize the GraphQL request when the component mounts. We’ll supply a unique key for the query, 'fetchFilms'
, and an asynchronous function that triggers a request to the GraphQL endpoint.
import { gql, request } from "graphql-request";
import { useQuery } from "@tanstack/react-query";
const allFilms = gql/* GraphQL */ `
...
`;
const App = () => {
const { data } = useQuery({
queryKey: ["fetchFilms"],
queryFn: async () =>
request(
"https://swapi-graphql.netlify.app/.netlify/functions/index",
allFilms,
{ first: 10 }
),
});
};
In the above code snippet, the useQuery()
hook is triggered when the App
component mounts, invoking the GraphQL query with the specified unique key and variables.
The useQuery()
hook returns a result
object that contains various properties that represent the state and outcome of the query. In addition to containing the data
fetched from the query when successful, the result
object also contains isLoading
and isError
values. isLoading
tracks the loading status of the request and isError
helps notify us when an error has occurred during the request.
With the isLoading
and isError
values, we can have the component render different elements depending on the state of our GraphQL request.
import { gql, request } from "graphql-request";
import { useQuery } from "@tanstack/react-query";
const allFilms = gql/* GraphQL */ `
query allFilms($first: Int!) {
allFilms(first: $first) {
edges {
node {
id
title
}
}
}
}
`;
const App = () => {
const { data, isLoading, isError } = useQuery({
queryKey: ["fetchFilms"],
queryFn: async () =>
request(
"https://swapi-graphql.netlify.app/.netlify/functions/index",
allFilms,
{ first: 10 }
),
});
if (isLoading) {
return <p>Request is loading!</p>;
}
if (isError) {
return <p>Request has failed :(!</p>;
}
return (
<ul>
{data.allFilms.edges.map(({ node: { id, title } }) => (
<li key={id}>
<h2>{title}</h2>
</li>
))}
</ul>
);
};
When our request is in-flight, the component will render a loading message.
If the request was to ever error, the component will render an error message.
Finally, if the request is not in-flight, no errors exist, and data is available from our request, we’ll have the component render the final intended output—a list of Star Wars films fetched from the API.
Test the above in this Codesandbox link.
React Query provides a useMutation() function to allow mutations to be conducted from React components. Unlike queries, mutations are used to create, update or delete data on the server or otherwise perform server side effects.
Like the useQuery()
function, the useMutation()
function receives an asynchronous function that returns a promise. The publicly accessible Star Wars GraphQL API we’re using doesn’t have root mutation fields for us to use but we’ll assume a mutation, called addFilm
, exists that allows us to add a new film to the list of films saved in our database.
import { gql, request } from "graphql-request";
import { useMutation } from "@tanstack/react-query";
const addFilm = gql`
mutation addFilm($title: String!, $releaseDate: String!) {
addFilm(title: $title, releaseDate: $releaseDate) {
id
title
releaseDate
}
}
`;
The addFilm
mutation will accept title
and releaseDate
as variables and when successful will return the id
of the newly created film, along with the title
and releaseDate
that were passed in.
In the component function, we’ll leverage the useMutation()
hook to help trigger the mutation when a button is clicked. We’ll call useMutation()
and supply the asynchronous function that triggers the GraphQL mutation request.
import { gql, request } from "graphql-request";
import { useMutation } from "@tanstack/react-query";
const addFilm = gql`
...
`;
const SomeComponent = () => {
const mutation = useMutation({
mutationFn: async (newFilm) =>
request(
"https://swapi-graphql.netlify.app/.netlify/functions/index",
addFilm,
newFilm
),
});
};
The useMutation()
hook returns a mutation
object that contains details about the mutation request (isLoading
, isError
, etc.). It also contains a mutate()
function that can be used anywhere in our component to trigger the mutation.
We’ll have a button trigger the mutate()
function when clicked. Additionally, we can display some messaging to the user whenever the mutation request is either in flight or has errored.
import { gql, request } from "graphql-request";
import { useMutation } from "@tanstack/react-query";
const addFilm = gql`
mutation addFilm($title: String!, $releaseDate: String!) {
addFilm(title: $title, releaseDate: $releaseDate) {
id
title
releaseDate
}
}
`;
const SomeComponent = () => {
const { mutate, isLoading, isError } = useMutation({
mutationFn: async (newFilm) =>
request(
"https://swapi-graphql.netlify.app/.netlify/functions/index",
addFilm,
newFilm
),
});
const onAddFilmClick = () => {
mutate({
title: "A New Hope",
releaseDate: "1977-05-25",
});
};
return (
<div>
<button onClick={onAddFilmClick}>Add Film</button>
{isLoading && <p>Adding film...</p>}
{isError && <p>Uh oh, something went wrong. Try again shortly!</p>}
</div>
);
};
This essentially summarizes the fundamentals of initiating projects with React and GraphQL. By using a GraphQL client library, we can leverage hooks and utilities to conduct GraphQL queries and mutations efficiently. The information fetched or manipulated using these queries and mutations allows us to display varied UI components and elements which ensures the application is dynamically responsive to user interactions and data alterations.
In this article, we explored the basics of integrating GraphQL with a React application, utilizing React Query as our client library. We wrapped our application in a QueryClientProvider
to make use of React Query functionalities in our application component tree and proceeded to make GraphQL queries and mutations using the useQuery()
and useMutation()
hooks, respectively.
Understanding the principles of GraphQL and how it integrates with React is important since it can sometimes offer a more efficient alternative to REST when dealing with APIs. By leveraging libraries like React Query, we can also significantly simplify the process of fetching, synchronizing, and managing the state of GraphQL server-side data in their React applications.
We’ll continue discussing GraphQL and React with some follow-up articles soon. Stay tuned!
Hassan is a senior frontend engineer and has helped build large production applications at-scale at organizations like Doordash, Instacart and Shopify. Hassan is also a published author and course instructor where he’s helped thousands of students learn in-depth frontend engineering skills like React, Vue, TypeScript, and GraphQL.