Telerik blogs

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.

GraphQL Client

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.

Getting Started

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>
);

Making Queries

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.

Handling Query Results

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.

Request is loading!

If the request was to ever error, the component will render an error message.

Request has failed :(

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.

List of Star Wars films

Test the above in this Codesandbox link.

Triggering Mutations

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
      ),
  });
};

Handling Mutation Results

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.

Wrap-up

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!


About the Author

Hassan Djirdeh

Hassan is currently a senior frontend engineer at Doordash. Prior to Doordash, Hassan worked at Instacart and Shopify, where he helped build large production applications at-scale. Hassan is also a published author and course instructor and has helped thousands of students learn in-depth fronted engineering tools like React, Vue, TypeScript and GraphQL. Hassan’s non-work interests range widely and, when not in front of a computer screen, you can find him at the gym, going for walks or running through the six.

Related Posts

Comments

Comments are disabled in preview mode.