Catching React query 101

We want our apps to load quickly. One way to achieve this is through caching.

By downloading and employing code in the form of applets or scripts, the React Query description expands client capabilities to save and reload data at a faster runtime.

As a result, you will be able to manage the state of your React apps in a consistent and dependable manner.

Redux was succeeded by a React query. It quickly manages the side effects of the API, caching, etc. using a simple boilerplate.

Contrarily, caching refers to keeping the server response in the client itself so that a client does not have to send server requests for the same resource repeatedly.

React Query is often described as the missing data-fetching library for React.

In more technical terms, it makes fetching, caching, synchronizing, and updating server state in your React applications a breeze.

Data from hardware or software can be cached and retrieved later. The API response can be cached when a client (like a browser) submits a REST API request.

You want API response data caching -

The server doesn't have to process that request again when the client makes it again, so the response time is quicker.

Prerequisite

The final project code can be found on my GitHub (remember to give it a star ⭐).

You should be familiar with the following;

  • Basic knowledge of React, JavaScript, and CSS at an intermediate level is required to follow along with this article.

  • Additionally, you must have node installed on your computer and have a basic understanding of npm packages.

  • A good code editor (VSCode for me), you can get started by downloading VScode Here

  • React Router, a library for routing in React, will also be used. Knowledge of React Router isn’t necessary, but you may want to check out the documentation.

  • Web Browser

  • Your brain :). Let’s get started!

What is React Query?

React Query is a set of hooks that can be used to fetch, cache, and update an asynchronous state in React. It is a simple and small API that can be used right away with no configuration.

It is protocol agnostic, which means we can use REST, GraphQL, or whatever protocol is appropriate for the use case, and it supports auto caching and re-fetching out of the box.

What is caching?

The great thing about React Query is that caching is handled "behind the scenes," so we don't have to worry about it.

Caching is a technique whose primary purpose is to increase the performance of data retrieval processes.

Also, reduce the costs associated with serverless processing and reduce server load.

Some basic principles peculiar to caching include the following;

  • Caching is the process of saving, archiving, or storing the result of a time-consuming action in order to retrieve it quickly (for reuse). This, in my opinion, is the best explanation for caching.

  • Get, set, and delete are the basic operations with a cache for a given key or value.

  • Typically, a value is cached with the TTL (time to live) parameter. This parameter specifies the time limit within which the value must be deleted from the cache in order to release cache space and refresh the cache.

  • Stale data, or data that is not the same as the original data, is a common problem with caches.

  • The key should be unique, as it will be used internally for re-fetching, caching, and deduping related queries.

  • The keys are passed as parameters to the query function in the order they appear on the key array.

If you do not understand this principle, it is easy to become overwhelmed and even misunderstood by caching technologies such as Redis and Memecache, Just keep in mind the bolded phrases, and you will be fine.

Caching can be grouped into two major types, namely:

Client Caching

Data can be cached on the client. For example, we can cache API call responses in the browser.

Then, we can use this cached data to provide a more seamless user experience by displaying cached information while making network requests to update the cache.

This caching type actually comes from end-user devices, such as a mobile app or a browser. In this case, caching serves only one purpose: to improve the quality of the user experience.

As a result, the questions of what should be cached and for how long should be directed toward improving the user experience rather than the system.

Server Caching

Data can also be cached on the server. When we make a request, we first check if the data is already in the cache.

If it does, we can return the data immediately; if not, we query the database and cache it for later use.

We may share ownership of this state with other applications because it is remotely persistent. It is asynchronous, so this means we’ll need to access it using asynchronous APIs.

Due to these factors, we cannot guarantee that this state is current with regard to our application.

We risk making trade-offs that favor one type of state over the other by combining server and client states in our global state.

The server cache state presents very specific difficulties that the client cache state does not.

These difficulties include things like caching, background updates, request deduplication, handling requests that are no longer valid, and others.

React Query was developed to address these issues and separate our server state from our client state.

When Do I Need To Catch My Data?

We need to consider the following to decide if the cache is needed:

  • The time it takes to load data

  • Resource consumption required

  • The result can be reused multiple times

Before making any decisions, keep those three issues in mind. For example,

It takes 500ms to obtain data from the database, but for only 1 request from a web page that nobody is interested => not yet cached.

A CSS code takes 100ms to build, but all users must load it. So yeah, you need to cache.

In a trade-off between speed and storage capacity, we use caching to use a limited storage solution for transient data, which is typically just a subset or a smaller chunk of another data object.

React query for data caching

To fully understand caching in ReactJS Query, follow these simple steps.

Creating React Application

Create a React application using the following command:

npx create-react-app pokeman

Step 2: Once your project folder, i.e., folder name, has been created, use the following command to move there:

cd pokeman

Now write down the following code in the index.js file. Here, the index is our default component, where we have written our code.

//index.js

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";
import PokemonPager from "./Pokemon";

function App() {
  return (
    <div className="App">
      <PokemonPager />
    </div>
  );
}

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

Fetch API Data

We can take a quick look at the data that most of our components will be fetching before we start writing any code. The file that acts as our API looks like this:

//fetchPokemon.js

export default ({ id }) =>
  fetch(`https://d1s1rehmg7ei44.cloudfront.net/api/v2/pokemon/${id}/`).then(
    res => res.json()
  );

Our Pokemon.js function, which we developed, creates a cached copy of the Pokemon information obtained from an API.

The query function must respect the following:

Function(variables) => Promise(data/error)

This is an exciting part because it means you can use Fetch, Axios, GraphQL, or whatever else you have in your application as long as it resolves the data you require.

As shown above, we use the fundamental React fetch function on the component ”fetchPokeman” to obtain data from an API in order to keep things simple.

First, we import the necessary CSS files and our data models into the main Pokemon.js component.

import React, { useState } from "react";
import fetchPokemon from "./fetchPokemon";
import { useQuery } from "react-query";

When using the cache library, a hook is available to be used to fetch data. It's called useQuery, and we will learn how to use it below:

Let's look at the useQueryhook as follows:

//pokemon.js

const Pokemon = ({ id }) => {
  const { data: pokemon, isLoading, error } = useQuery(
    ["pokemon", { id }],
    fetchPokemon
  );

Above, we created a fetchPokman function that will be used to convert the API result into an object that we can use in our application.

Every call to the useQuery method must be done with a unique key and a function for resolving the data.

The key must respect the following types:

String | [String, Variables: Object] | falsy | Function => queryKey

When this line is used as the first line in the code, it makes a standard request and caches the data.

The data is then immediately returned if you call it from a different component (with the same key), while new data is requested in the background and updated when it is ready.

Before we dive deep into the useQuery hook, shall we handle the API result and populate our grid?

The API returns a lot of data, but we only need the ID and Pokemon data (images and name).

return isLoading ? (
    <div>loading...</div>
  ) : !pokemon ? (
    <div>{error}</div>
  ) : (
    <div>
      <h2>#{id}</h2>
      <h2>{pokemon.name}</h2>
      <img alt="pokemon" src={pokemon.sprites.front_shiny} />
    </div>
  );
};

Next, let's modify our codebase to support button movement to create a change when we click on the button as below;

//pokeman.js

export const PokemonPager = () => {
  const [id, setId] = useState(1);
  return (
    <div>
      <button type="button" onClick={() => setId(id !== 1 ? id - 1 : 250)}>
        Previous
      </button>

      <button type="button" onClick={() => setId(id !== 250 ? id + 1 : 1)}>
        Next
      </button>

      <Pokemon id={id} />
    </div>
  );
};

When we click on the next or previous button, the function is triggered, data gets stored in the cache, and we see how the “loading...” pop disappears.

How React Query Cache Works Under The Hood

It is critical to understand why useQuery() works the way it does.

Let's open our React dev tools, and I'd like you to watch what happens on the screen when we navigate using the previous and next buttons.

In this case, we desire a slower network speed, so I'll alter the throttle to fast 3G and then check that the server is still operational.

When you click next, you will see a loading message before the next Pokemon image is displayed.

However, if we click on the previous and next images again, the loading text disappears, which is due to the query cache provided by the React query.

Every query result is cached by default for 5 minutes, and React query rely on this.

When useQuery is fired for the first time for the next button key, isloading is set to true, and a network request is sent to fetch the data.

When the request is finished, the data is cached using the query key and the fetchpokeman() function as unique identifiers.

Using the default cacheTime of 5 minutes and the default staleTime of 0, when the network request has been completed, the returned data will be cached under the ["fetchPokemon"] key.

After the configured staleTime (which defaults to 0, or immediately), the hook will mark the data as stale.

Now, when you manipulate through the dev tools, react query checks to see if the data for the query exists in a cache, and if it does, the given data is immediately returned without isloading set to true, which is why we don't see the loading text for subsequent requests.

Because our data is identical to the cached data, we observe no difference in the UI.

Trust me. Your work life will be significantly simplified by React Query DevTools. Finding out when a query is re-fetched, what data it contains, or how many times it was retried is such a gain.

In order to better understand the dev tool environment as we explore the React dev tools, let's make note of a few fundamental ideas as we proceed:

  • enabled: by default, true, but can be set to false to prevent the query from running. The query can then be triggered conditionally. Also useful when querying with multiple parameters and not knowing whether or not a parameter is available while querying.

  • cache-time: Measures time in milliseconds and caches data for that period. The duration is set to 5 minutes by default. It should be noted, however, that React query will run background fetch on every component re-render to determine if the cached data is not stale.

  • staleTime: The amount of time it would take for our data to become stale. React query will not perform background re-fetches until the stale time for data is reached, which will occur when the component is re-rendered.

  • onSuccess(data): accepts a function to execute after the API has successfully returned a response.

  • onError(data): accepts a function and executes it after the API has failed to return a response.

  • select(data): takes a function and returns data from the API response. We can alter data, add to it, delete it, or serialize it in any way we like. This function's output will be returned as data via the useQuery hook.

Conclusion

I believe we have got to a good level of understanding of React Query 👌

React query caching can occur on both the client and server sides, as well as for application data and static assets.

We must ensure that the cache is up-to-date with data and that we are making the best use of our limited space.

I hope I have given you the motivation to continue learning React Query. With all that we learned today, you are more than ready to start using it in your next project!

On the main branch of this GitHub repo, you will find the final solution with all the code examples we did together!