Using Suspense with react-query
Tags: react, javascript, webdev
Published: 2020-01-12 16:25:26

---
title: Using Suspense with react-query
published: true
date: 2020-01-07 14:00:45 UTC
tags: react, javascript, webdev
canonical_url: https://blog.logrocket.com/using-suspense-with-react-query/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/y9uresgtd0gdif9xb162.jpeg
---
**Written by [Abdulazeez Abdulazeez Adeshina](https://blog.logrocket.com/author/abdulazeezadeshina/)**✏️

Suspense isn’t exactly a new feature in the React ecosystem. However, if you don’t know what Suspense is all about or you’re just starting out with React, you should [have a look here](https://dev.to/bnevilleoneill/the-future-of-react-unfolding-with-suspense-490g).

In a bid to make writing React components easier and with less code, Hooks were introduced to manage states in functional apps — that’s also not a new feature. Despite these improvements to React, one major functionality is still missing: caching.

In this article, we’ll look at using the [react-query](https://github.com/tannerlinsley/react-query) library alongside Suspense by building a simple recipe app that fetches recipe data from an API and renders it to the DOM.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## What is `useQuery`?

React-query’s `useQuery(query, fn)` is a Hook that fetches data based on the query passed into it and then stores the data in its parent variable. A query, in this case, consists of a unique key and an asynchronous function that is acted upon. The unique key passed into the query is used for internal operations such as fetching data, caching, and refetching data linked to the query.

The Hook library can be installed via npm or Yarn:

```jsx
yarn add react-query

// or

npm i -s react-query
```

Now, say you want to test react-query’s Hook by fetching some data from a particular source. The Hook is stored in a variable `query` (the default style):

```jsx
const query = useQuery("demo", fetchQueries)

// fetchQueries() 

async function fetchQueries() {
  return (await fetch(`http://some-url.com/endpoint`))
}
```

When used, the query variable is stored with information returned from the asynchronous function `fetchQueries`.

## `useQuery()`’s features

If you need to fetch data from a source — an API, for example — you usually need to create a request in the `useEffect()` Hook, in `componentDidMount`, or in another function, and this request is run every time your app reloads. This is quite stressful, and this is where react-query comes into play.

### Fetching data

The basic feature of `useQuery()` is fetching data. We’ll see from a simple demo how the data fetching aspect works.

First, you define the component and store the result from our `useQuery` into three destructurable variables:

```jsx
function Recipes() {
   const { data, isLoading, error } = useQuery('recipes', fetchRecipes)

  return (
      <div>

      </div>
  )
}
```

The three variables to be destructed will contain the returned information as named:

1. The `data` variable holds the data returned from the `fetchRecipes` function
2. The `isLoading` is a Boolean variable that holds the running status of the Hook
3. The `error` variable holds whatever error is sent back from the Hook

Next, the received information is displayed by adding this block of code into the `<div>` body:

```jsx
function Recipes() {

  ...
  <div>
    { isLoading ? (
      <b> Loading .. </b>
    ) : error ? (
      <b>There's an error: {error.message}</b>
    ) : data ? (
      <ul>
        {data.map(recipe => (
          <li key={recipe.id}>{recipe.title}</li>
        ))}
      </ul>
    ) : null }
  </div>
  ...
}
```

The block of code above conditionally renders data from `useQuery()` using the ternary operator. If you’re a seasoned React developer, this shouldn’t be new to you. But if you’re a beginner, then you should have [basic knowledge of conditional rendering](https://blog.logrocket.com/conditional-rendering-in-react-c6b0e5af381e/) in JavaScript as well as React.

> The ternary operator is a shorthand method to the native `if-else`.

So the code above:

1. Checks the loading status of the query from the Boolean variable `isLoading`
2. Displays a loading message if the variable reads true. Otherwise, display an error if there’s an error message in the error object
3. If there is no error message, displays the data if it isn’t empty (or has been created by the query)
4. Otherwise, returns a default `null` object, leaving the page blank if none of the above conditions are met

The idea of leaving the page blank isn’t ideal, but we’ll see how we can return relevant messages when there isn’t any data loaded.

### Prefetching

Prefetching is one of the most interesting features in react-query. It works the same way as fetching data in that it is loaded from inception from either your `useEffect()` or `componentDidMount()` method.

In this case, data is loaded and stored in cache so your app doesn’t have to send a new request to retrieve data each time a user needs it.

### Caching

Caching simply means storing data for a period of time. Caching is a superb feature from react-query and allows your app to retrieve data from memory once it’s cached without having to re-query. You can learn more about the caching feature [here](https://github.com/tannerlinsley/react-query#caching--invalidation).

## Building the app

We’ll be building a simple recipe app that fetches and render data from an API using react-query’s `useQuery()` Hook. I’ll assume you are familiar with React Hooks — otherwise, [check here](https://blog.logrocket.com/react-hooks-cheat-sheet-unlock-solutions-to-common-problems-af4caf699e70/). All the code for this article can be found in [this GitHub repo](https://github.com/Youngestdev/react-query-app), as well.

Let’s get started!

![Recipe App Preview](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/01/recipe-app-preview.png?resize=730%2C444&ssl=1)<figcaption id="caption-attachment-11994">The recipe app we’re building.</figcaption>

### Setup

The first step in building our app is to set up a working directory by installing our required dependencies and creating the required files. To set up the working directory from your terminal in your preferred root directory, run the following commands:

```jsx
mkdir react-query-app && cd react-query-app
mkdir api public src src/components
cd public && touch index.html style.css
cd ../src && touch index.jsx queries.jsx
cd components && touch Button.jsx Spinner.jsx Recipe.jsx Recipes.jsx
cd ../../api && touch app.js
```

Next, we install the required dependencies:

```jsx
npm install react react-dom react-query react-scripts
```

We didn’t use create-react-app to set up our app because it’s a little demo, and we don’t want unnecessary excess files.

Next thing is to add a `start` section to our `package.json` script section to run and render our app:

```jsx
...

"start" : "react-scripts start"
```

Since we didn’t use CRA to bootstrap our app, we have to create an `index.html` file in the public folder:

```jsx
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/csS" href="style.css" />
  <link href="https://fonts.googleapis.com/css?family=Sedgwick+Ave&display=swap" rel="stylesheet"> 
  <link href="https://fonts.googleapis.com/css?family=Arvo|Copse&display=swap" rel="stylesheet"> 
</head>
<body>
  <div id="root">
  </div>
</body>
</html>
```

Next, we’ll style our app:

```jsx
body {
  background-color: #f0ebeb;
  font-family: 'Sedgwick Ave', cursive;
  font-size: 16px;
}
h1 {
  font-size: 40px;
  font-weight: lighter;
}
h2 {
  font-size: 20px;
}
button {
  background-color: #c8d2ddf3;
  border-radius: 12px;
  border: 5px 10px;
  font-family: 'Arvo', serif;
}
p {
  font-size: 18px;
  font-family: 'Copse', serif;
}
```

### API

Let’s start our app by building the backend API where we’ll fetch data. We’ll start by installing the dependencies:

```jsx
npm init -y // initialize the repo first
npm i express cors body-parser
```

Now we’ll write the backend code in the `app.js` file we created earlier.

#### `app.js`

This is the where the app’s backend code will be written. In this file, a simple route and static JSON data is filled into an array where, upon using the GET method, it returns data from the static JSON. The code contained in `app.js` is:

```jsx
// import necessary dependencies

const express = require("express");
const bodyParser = require("body-parser");
const cors = require('cors')

// initialize express.js
const app = express();

app.use(bodyParser.json());
app.use(cors())

// hardcoded recipes

const recipes = [
  {
    id: 1,
    title: "Jollof Rice Recipe",
    content: "How to make jollof rice ..."
  },
  {
    id: 2,
    title: "Bacon and Sauced Eggs",
    content: "How to make bacon and sauced eggs"
  },
  {
    id: 3,
    title: "Pancake recipes",
    content: "how to make pancakes..."
  },
  {
    id: 4,
    title: "Fish peppersoup recipe",
    content: "how to make it..."
  },
  {
    id: 5,
    title: "Efo Riro",
    content: "how to make it..."
  },
  {
    id: 6,
    title: "Garden Egg soup",
    content: "how to make it..."
  }
];

// return all recipes
app.get("/", (req, res) => {
  res.send(recipes);
});

// return a single recipe by ID
app.get("/:id", (req, res) => {
  const recipe = recipes.filter(
    recipe => recipe.id === parseInt(req.params.id)
  );
  if (recipe.length === 0) return res.status(404).send();
  if (recipe.length > 1) return res.status(500).send();
  res.send(recipe[0]);
});

app.listen(8081, () => {
  console.log("App's running on port 8081");
});
```

The backend code, as stated earlier, contains a hardcoded recipes array and simple routes. The backend simply receives requests, parses them to JSON with the aid of `body-parser`, and returns the data in JSON format. The backend API receives only two requests:

1. `"/"`: When a request is directed to this, the backend returns all data in the recipes array
2. `"/:id"`: When a request is directed to this with `:id` replaced with an integer, it returns a recipe whose ID corresponds with it

Interestingly, that is all the backend code since we said we’ll be building a _simple_ recipe app. Let’s move on to building the frontend part of our app, where we will get to see how react-query works with Suspense.

## Components

So, we have successfully built the backend part of our app, from which data will be retrieved. Now we have to build the frontend part of our app, where data will be displayed or rendered.

### `index.jsx`

This is the file that mounts our React app and renders our data.

```jsx
import React, { lazy } from "react";
import ReactDOM from "react-dom";

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement)
```

This is a basic render file. Next, we import `react-query` and the recipe components since we’ll be writing the main app component, `<App />`, in the `index.jsx` file:

```jsx
import { ReactQueryConfigProvider } from "react-query";

const Recipes = lazy(() => import("./components/Recipes"));
const Recipe = lazy(() => import("./components/Recipe"));

const queryConfig = {
  suspense: true
};
```

We imported react-query’s configuration context provider and also created a `queryConfig` object that indicates that we are going to use Suspense in our app alongside react-query. Next, we’ll write our `App` component:

```jsx
function App() {
  const [activeRecipe, setActiveRecipe] = React.useState(null);

  return (
  <React.Fragment>
    <h2>Fast Recipes</h2>
    <hr />
    <ReactQueryConfigProvider config={queryConfig}>
        <React.Suspense fallback={<h1> Loading ...</h1>}>
          {  activeRecipe ? (
              <Recipe
                activeRecipe={activeRecipe}
                setActiveRecipe={setActiveRecipe}
              />
            ) : (
              <Recipes setActiveRecipe={setActiveRecipe} />
            )}
        </React.Suspense>
    </ReactQueryConfigProvider>
  </React.Fragment>  
  );
}
```

In our app component, we initialized a state named `activeRecipe` and the state handler `setActiveRecipe`, and then created a title for our app and grouped children tags under `React.Fragment`.

Next, we loaded react-query’s configuration provider component and passed the config object `queryConfig` that tells react-query we will be using Suspense.

Next, we wrap the conditional rendering under `React.Suspense`. If `activeRecipe` is set to true, it displays the recipe; otherwise, it displays the list of recipes.

We also added a `fallback` prop to `React.Suspense`. This is a required prop that renders the passed data whenever there isn’t any data to be rendered or if there’s a delay in fetching data.

Without the addition of Suspense, react-query renders a blank page when it is in the process of querying and rendering data. This isn’t ideal, as such situations don’t give users any indication of what the app is doing at that instance.

Next, we write the queries react-query will deal with in `queries.jsx`.

### `queries.jsx`

```jsx
export async function fetchRecipes() {
  return (await fetch(`http://localhost:8081`)).json();
}

export async function fetchRecipe({ id }) {
  return (await fetch(
    `http://localhost:8081/${id}`
  )).json();
}
```

The `fetchRecipes()` function returns the list of all recipes when queried, and `fetchRecipe` returns only a recipe.

Next, we’ll write the component that renders a single recipe.

### `Recipe.jsx`

```jsx
import React from "react";
import { useQuery } from "react-query";

import Button from "./Button";

import { fetchRecipe } from "../queries";
```

First, we import React and `useQuery` from its library to give us access to its features. We also import secondary components that handle little things, as we will see later on.

Next, we write the component after the import statements:

```jsx
export default function Recipe({ activeRecipe, setActiveRecipe }) {
  const { data, isFetching } = useQuery(
    ["recipe", { id: activeRecipe }],
    fetchRecipe
  );

  return (
    <React.Fragment>
      <Button onClick={() => setActiveRecipe(null)}>Back</Button>
      <h1>
        ID: {activeRecipe} {isFetching ? "Loading Recipe" : null}
      </h1>
      {data ? (
        <div>
          <p>Title: {data.title}</p>
          <p>Content: {data.content}</p>
        </div>
      ) : null}
      <br />
      <br />
    </React.Fragment>
  );
}
```

The `Recipe` component takes two props, `activeRecipe` and `setActiveRecipe`, which will be used by the `useQuery` Hook to query and render data.

The `useQuery` Hook took two arguments: `(["recipe", { id: activeRecipe }], fetchRecipe)`.

The first argument is an array that consists of a query name and a unique identifier, which, in this case, is the `{ id: activeRecipe }`.

The unique identifier is used by the app when querying data through the second argument, `fetchRecipe`. The Hook is saved into a destructurable object:

1. `data`, which will contain the information returned by the second argument, `fetchRecipe`
2. `isFetching`, which is a Boolean that tells us the loading state of the app

The component renders the recipe data once there’s data returned from the `useQuery` Hook as shown on lines 13–18; otherwise, it renders nothing. The data is in turn cached, and if the user goes back and clicks on the same recipe, a new request won’t be sent. Instead, the recipe is displayed immediately, and about twice as fast as when a request is sent.

There is also a `Button` component that allows the user to navigate easily within the app. Next thing we’ll do is build the `Recipes` component.

### `Recipes.jsx`

The `Recipes` component is responsible for the rendering of the list of recipes queried from `fetchRecipes` using `useQuery()`. The code responsible for that is:

```jsx
import React from "react";
import { useQuery, prefetchQuery } from "react-query";

import Button from "./Button";

import { fetchRecipes, fetchRecipe } from "../queries";

export default function Recipes({ setActiveRecipe }) {
  const { data, isFetching } = useQuery("Recipes", fetchRecipes);

  return (
    <div>
      <h1>Recipes List 
      { isFetching 
        ? "Loading" 
        : null 
      }
        </h1>
      {data.map(Recipe => (
        <p key={Recipe.title}>
          <Button
            onClick={() => {
              // Prefetch the Recipe query
              prefetchQuery(["Recipe", { id: Recipe.id }], fetchRecipe);
              setActiveRecipe(Recipe.id);
            }}
          >
            Load
          </Button>{" "}
          {Recipe.title}
        </p>
      ))}
    </div>
  );
}
```

In the component, we started off by importing React and react-query to enable us to use the `useQuery` Hook.

A loading message is displayed when the data is being fetched. The `useQuery()` Hook is used to retrieve the list of recipes from the backend.

Traditionally, this would have been done in the `useEffect()` Hook like this:

```jsx
const [data, setData] = useState([])

useEffect(() => {
  fetch('https://api-url/recipes')
      .then(response => response.json())
      .then(data => {
        setData(data); // save recipes in state
      });
}, [])
```

Behind the scenes, this is the process carried out by react-query.

Next, the data retrieved from react-query is cached, mapped out from its array, and then rendered on the DOM.

The code for the helper component `Button` follows below.

### `Button.jsx`

```jsx
import React from "react";

export default function Button({ children, timeoutMs = 3000, onClick }) {

  const handleClick = e => {
      onClick(e);
  };

  return (
    <>
      <button onClick={handleClick}>
        {children}
      </button>
    </>
  );
}
```

## Running our app

Next thing is to preview the app we’ve been building. We’ll start by running the app first without the backend to verify that a blank page will be displayed when no data is returned. From your terminal, start the React app:

```jsx
npm run start
```

Next, open your web browser and navigate to `http://localhost:3000`, and you should get a page like this:

![Timeout Displaying Blank Page](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/01/recipe-app-timeout-blank-page.gif?resize=730%2C441&ssl=1)

We get a blank page after the timeout (~1000ms) since the app has nothing to render to the DOM.

Next, we start our backend app by running the command below from the `api` folder:

```jsx
npm run start

// or

node app.js
```

Once our backend app starts running, we get a notification from the terminal, and then we refresh the browser at localhost to render our recipes:

![Recipe List Rendered](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/01/recipes-rendered.png?resize=730%2C444&ssl=1)

Suspense is said to inform the user of the app’s status when fetching or loading data from a source. In this case, react-query fetches data, and Suspense keeps us updated with the app status as instructed in the `App` component.

However, we haven’t seen the real effect of Suspense since the app loads fast. Setting the browser’s connection to 3G and refreshing the browser renders **Loading…** for a long time.

This is because the app is still awaiting data from the backend (i.e., the fetch status is pending), and therefore, Suspense displays the fallback message to avoid rendering a blank page. The page renders the recipes once the data is fetched.

![React Suspense Rendering Loading State](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/01/suspense-loading-state.gif?resize=730%2C339&ssl=1)<figcaption id="caption-attachment-11997">Suspense in action.</figcaption>

We have successfully implemented Suspense in our react-query app.

Also, when a recipe is being loaded, the Suspense fallback message is displayed when there’s a delay in data fetching. The fetched recipe data is stored in cache and is immediately displayed again if the same recipe is loaded again.

![Recipe Loading From Cache](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/01/react-query-cache.gif?resize=730%2C441&ssl=1)<figcaption id="caption-attachment-11998">React-query’s caching feature.</figcaption>

## Conclusion

In this article, we’ve taken a look at what Suspense and react-query are all about, plus the various features of react-query’s `useQuery` Hook by building a simple recipe app.

Lastly, you can find the code for the app built in this article [here](https://github.com/Youngestdev/react-query-app). Happy coding ❤.

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [Using Suspense with react-query](https://blog.logrocket.com/using-suspense-with-react-query/) appeared first on [LogRocket Blog](https://blog.logrocket.com).