How to use React Query with React and GraphQL

Ibrahima Ndaw

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

React Query is a library that provides a set of hooks for fetching, caching, and updating data in your React applications. In this tutorial, we will be looking at React Query and learning how to use it in a React and GraphQL app.

What is React Query?

React Query (RQ) is a performant and powerful data synchronization library for React apps. It provides a collection of hooks for fetching and managing data. It is backend agnostic, which means you can use REST, GraphQL, or whatever APIs you like, RQ doesn’t care. React Query handles caching, background updates, and stale data out of the box with zero-configuration. The caching layer of RQ is powerful and minimal effort to configure.

React Query makes state managing easy because it allows you to fetch, mutate and cache data with almost no hassle. And can also be customized for more advanced use cases. While React Query does a lot for you, it’s not a complete replacement for client-state management libraries because RQ can’t handle UI state (state for controlling the interactive parts of our app); it’s a library for fetching and synchronization data.

However, RQ is designed to replace the boilerplate code and related wiring used to manage cache data in your client-state and replaces it with just a few lines of code. RQ has to manage asynchronous operations between your server and client and use Redux, MobX, Zustand, or even React Context to handle the UI state. This way, you will get a simplified app logic and delivers a snappy experience to your users with less code.

What we’re building

In this guide, we will be building a Blog app using React, React Query, and GraphQL. We will retrieve the data from the TakeShape GraphQL API. Let’s get started!

Setting up

Before we craft up a new React app, we need to sign up for an account on TakeShape (it’s free) then create a new project to get a GraphQL API to play with. After you create an account and create a read-only API key, open your command-line interface and run the following:

npx create-react-app rq-graphql-app

This command will create a new React app for us. Next, we need to install a couple of libraries. Browse to the project root and do the following:

npm install react-query react-router-dom graphql graphql-request react-markdown

Here’s what each of the libraries you are installing does:

  • react-query allows interacting with the GraphQL API and retrieve the data.
  • react-router-dom enables routing in our app.
  • graphql is a dependency for graphql-request.
  • graphql-request allows fetching data from a GraphQL backend.
  • react-markdown helps render Markdown in a React app.

With the dependencies installed, we can get our hands dirty and see React Query in action.

Folder structure

Structure your project as follows:

├── src
|  ├── components
|  |  ├── Header.js
|  |  ├── Layout.js
|  |  ├── Post.js
|  |  └── PostTemplate.js
|  ├── App.js
|  ├── useRequest.js
|  ├── index.css
|  ├── index.js
├── .env
├── package.json
└── yarn.lock

Take special note of the useRequest.js file. It is a custom hook that uses RQ to retrieve the data from the TakeShape GraphQL API. This file is where the magic happens; it helps interact with the API to fetch the blog posts. You can alternatively use the RQ hooks in your components, but it’s nice to have a custom hook to avoid repeating ourselves.

Next, let’s configure our app to use React Query.

Setting up React Query

// index.js
import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "react-query";

import App from "./App";
import "./styles.css";

const queryClient = new QueryClient();

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  rootElement
);

In order to use the hooks of RQ to interact with our GraphQl API, we need to wrap our top-level app component with the RQ library query provider.

Using React Query

// useRequest.js
import { useQuery } from "react-query";
import { GraphQLClient, gql } from "graphql-request";

const API_URL = `https://api.takeshape.io/project/${process.env.PROJECT_ID}/v3/graphql`;

const graphQLClient = new GraphQLClient(API_URL, {
  headers: {
    Authorization: `Bearer ${process.env.API_KEY}`
  }
});

export function useGetPosts() {
  return useQuery("get-posts", async () => {
    const { getPostList } = await graphQLClient.request(gql`
      query {
        getPostList {
          items {
            _id
            title
            description
            content
          }
        }
      }
    `);
    return getPostList;
  });
}

export function useGetPost(postId) {
  return useQuery(["get-post", postId], async () => {
    const { getPost } = await graphQLClient.request(
      gql`
        query getPost($postId: ID!) {
          getPost(_id: $postId) {
            _id
            content
            description
            title
          }
        }
      `,
      { postId }
    );
    return getPost;
  });
}

In useRequest.js, we start by importing the useQuery hook and graphl-request. Next, we declare the API_URL constant with the credentials provided by TakeShape. For each request, we need to include an authorization header with the API key from TakeShape in order to authenticate to the GraphQL API . Using GraphQLClient allows us to set the API key on each request.

To get all blog posts from the API, we use the useGetPosts function. The useQuery hook expects a key (get-posts) and a GraphQL query. The hook can receive more options, but for this example, we just need these two. Once the fetch is done, we return the data. React Query will append some data to the returned value, which allows handling loading and error states.

Next, useGetPost, receives the id of the post to fetch. To pass in the id to the GraphQL query, we need to add it as a second argument to the request() method. With this, the data is fetched and then returned.

The custom hook is ready to use. Let’s create the React components and rely on the hook to retrieve the data.

Creating the components

// components/Post.js
import React from "react";
import { Link } from "react-router-dom";
export default function Post({ article }) {
  const { _id, title, description } = article;
  return (
    <article className="Article">
      <h1>{title}</h1>
      <p>{description}</p>
      <Link to={`/single-post/${_id}`}>Read more &rarr;</Link>
    </article>
  );
}

This component is responsible for the display of a blog post preview. It receives the object as a parameter and then shows it accordingly.

// components/PostTemplate.js
import React from "react";
import ReactMarkdown from "react-markdown";
import { useParams } from "react-router-dom";
import { useGetPost } from "../useRequest";
export default function PostTemplate() {
  const { id } = useParams();
  const { data, error, isLoading, isSuccess } = useGetPost(id);

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

  return (
    isSuccess && (
      <article className="Post">
        <h1>{data.title}</h1>
        <ReactMarkdown source={data.content} />
      </article>
    )
  );
}

PostTemplate.js is a template to display a blog post. The id is pulled out from the router params and then passed in to the useGetPost hook. With this, we can now get the post using its id. It returns the data and some states provided by RQ to handle the case when something went wrong.

Showing the blog posts

// App.js
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";

import { useGetPosts } from "./useRequest";
import Post from "./components/Post";
import PostTemplate from "./components/PostTemplate";
import Layout from "./components/Layout";

export default function App() {
  const { data, error, isLoading, isSuccess } = useGetPosts();

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

  return (
    <Router>
      <Layout>
        <Route path="/" exact>
          {isSuccess &&
            data.items.map((post) => <Post key={post._id} article={post} />)}
        </Route>
        <Route path="/single-post/:id">
          <PostTemplate />
        </Route>
      </Layout>
    </Router>
  );
}

In App.js, we import the custom hook and use it to get all blog posts from the API. Then loop through the response data and display the posts using the Post component.

We are now ready to test our example app in the browser. Open the project directory in your CLI and run:

npm start

If everything works as it should, the app will be up and running here: [<http://localhost:3000/>](<http://localhost:3000/>).

app-preview

Awesome! Our Blog app is looking nice.

React Query comes with dedicated devtools. It helps visualize all of the inner workings of React Query and will likely save you hours of debugging. To enable it, we need to enable it in index.js.

// index.js
import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

import App from "./App";
import "./styles.css";

const queryClient = new QueryClient();

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  </React.StrictMode>,
  rootElement
);

Import ReactQueryDevtools from react-query and add it as a child component of the QueryClientProvider component. That’s it! The devtools are ready to use. Let’s try it in the browser.

devtools

Once you click on the React Query logo, the devtools will pop up with its neat features. Enjoy!

You can find the finished project in this CodeSandbox. Thanks for reading!

Conclusion

React Query is a useful library for managing asynchronous operations between your server and client. It continues to gain traction and is trusted in production by large companies such as Google, Facebook, Amazon, and Microsoft. Folks are using it to simplify their state management because it has great caching strategies, can synchronize and updates server state, and has less boilerplate. RQ is a go-to for your next project that requires remote data fetching.