Making Use of React Query in Fetching Data and Adding Pagination for Performance Optimization in React.

Making Use of React Query in Fetching Data and Adding Pagination for Performance Optimization in React.

A comprehensive guide

Developers must create their own methods of data fetching because React apps do not come with a predetermined way of fetching or updating data from your components out of the box. While the majority of conventional state management libraries are excellent at handling client states, they are less effective when handling async or server states. This is due to the entirely different server state. The server state is first:

  • kept remotely in a place that you don't own or have control over.

  • calls for asynchronous APIs to fetch and update data.

  • implies shared ownership and is subject to unauthorized alteration by others.

  • can potentially cause your applications to become "out of date" if you're not careful.

Even additional difficulties will materialize as you proceed after you understand the nature of the server state in your application, for instance:

  • Caching.. (possibly the hardest thing to do in programming).

  • Combining several requests for the same data into one request using deduplication.

  • Background updating of "out of date" data.

  • Recognizing "out of date" data.

  • Updating data as rapidly as feasible to reflect changes.

  • Performance improvements such as pagination and lazy data loading.

  • Managing server memory and garbage collection.

  • Utilizing structural sharing to memorize query results.

In this tutorial, I will show you how to use react query to manage your server-side rendering in your react app and optimize your app for greater performance by making use of pagination.

Prerequisite

  • At least a knowledge of React(Beginners are welcome) and JavaScript is required.

  • node installed to be able to download the package using npm.

Setting Your App and Necessary Dependencies for the Project

For this project, you will not be making use of create-react-app instead, you will be making use of vite and react plugins which are faster than create-react-app. Check out this article for more details.

To create your app run the code below in the terminal:

npm create vite@latest my-app --template react

Next, run the following commands in the terminal

cd my-app
npm install
npm run dev

That will load up the development server which can be found in localhost:3000

Install the following package you will be using for this project

  1. react-query which is the library you will be using for async server state management
npm install react-query
  1. axios which you will be using to fetch data from our api.
npm install axios

Getting Started: Making use of React Query

You will be fetching your data from the star-war-api. Swapi is an entirely unrestricted API. To query and get data, no authentication is necessary. **

First, you will have to create a components folder inside the src directory and create these two jsx files Planets.jsx and Planet.jsx

Then inside your app.jsx file, copy and paste the code below:

From the code below, the QueryClientProvider wrapped our app and took in a value which we defined above as the queryClient. One unique feature of React Query is that it does not work without the QueryClientProvider wrapping the app it is being used on.

React Query provide dev tools that allow you to see what happens behind the scene when you fetch data from an api. They aid in the visualization of all of React Query's internal operations and, should the need arise, might spare you countless hours of troubleshooting! From the code React Dev tool is set to close and can be found with the react-query icon at the bottom of your app.

//app.jsx
import Planets from "./components/Planets";
import { QueryClient, QueryClientProvider } from "react-query"
import { ReactQueryDevtools } from "react-query/devtools";
import "./App.css"

// Initialising a  new Queryclient and Storing it with the variable name queryClient
const queryClient = new QueryClient()

function App () {

  return (
    <QueryClientProvider client={queryClient}>
    <div className='App'>
      <h1>React Query using Star War API</h1>
     <div className="content">
         <Planets/>
      </div>
    </div>
        // React Query Devtools which we gives ou access to the data you will be fetching it         is set to close
    <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

export default App;

Copy and paste the code below into the Planets.jsx file you created.

From the code, you can see that we used the useQuery hooks. When making use of this hook you have to call the useQuery hook with at least:

  • A unique key for the query.

  • An asynchronous function that either resolves the data or throws an error and returns a promise.

Internally, your application uses the unique key you supply to fetch, cache, and distribute requests.

In this case “planets” is the ****unique key while “fetchPlanets” is the asynchronous function used in fetching the data from the api.

//Planets.jsx
//Importing useQuery from the react-query library 
import { useQuery } from 'react-query';
import axios from "axios"

    //This is an asynchronous function which is used in fetching from the api 
const fetchPlanets = async () => {
    const res = await axios.get('https://swapi.dev/api/planets/');
    return res.data;
  }

const Planets = ()=>{
    const { data, error, status } = useQuery('planets', fetchPlanets);
        //To check if we have our data if yes it will be displayed in the console
    console.log(data)

    return (
        <div></div>
    )

}

export default Planets

Save the file and you will have a result like this.

The annotated part of the image is referencing the react dev tools which when click will give the details of the data fetched.

First Part.png

Annotated Explanation Showing the React Dev tool icon

You can check what you have in the console.

Console Showing Data

Aha! The data is being displayed in the console signifying that your fetching is successful.

You can also check for your data using the react dev tools

React Dev tools

Now you should display the data in your app. Copy and paste the code below into your created Planet.jsx file.

In this file a functional component is created which accepts the props planet. it will be imported into the Planets.jsx file

//Planet.jsx
const Planet = ({ planet }) => {
    return (
      <div className="card">
        <h3>{ planet.name }</h3>
        <p>Population - { planet.population }</p>
        <p>Terrain - { planet.terrain }</p>
      </div>
    ); 
  }

  export default Planet;

While in the Planets.jsx file a few lines of code is being added as shown below. From the code, it shows that you are mapping through the data and each iteration is passed as a prop to the Planet component.

As explained in the React Query docs a query can only be in one of the following states at any given moment:

  • isLoading or status === 'loading' - The query has no data and is currently fetching.

  • isError or status === 'error' - The query encountered an error.

  • isSuccess or status === 'success' - The query was successful and data is available.

  • isIdle or status === 'idle' - The query is currently disabled (you'll learn more about this in a bit)

Some of the parameters used in the code can be explained below:

  • error - If the query is in an isError state, the error is available via the error property.

  • data - If the query is in a success state, the data is available via the data property.

  • isFetching - In any state, if the query is fetching at any time isFetching will be true.

//Planets.jsx
import { useQuery } from 'react-query';
import axios from "axios";
import Planet from './Planet';

const fetchPlanets = async () => {
    const res = await axios.get('https://swapi.dev/api/planets/');
    return res.data;
  }

const Planets = ()=>{
    const { isFetching, data, status, error } = useQuery('planets', fetchPlanets);
    console.log(data)

    return (
    <div>
        <h2>Planets</h2>
            {/* Signifying if it is in a loading state */}
      {status === 'loading' && (
        <div>Loading data</div>
      )}
            {/* Signifying if it encounters an error on the way */}
      {status === 'error' && (
        <div>Error: {error.message}</div>
      )}
            {/* Signifying if the fetching was successful*/}
      {status === 'success' && (
        <>
          <div>
            { data.results.map(planet => <Planet key={planet.name} planet={planet} /> ) }
          </div>
        </>)}
    </div>
    )

}

export default Planets

Add some CSS to your App.css file to make the app more appealing.

/*App.css*/
body {
  margin: 0;
  font-family: sans-serif;
  background: #222;
  color: #ddd;
  text-align: center;
}
.App{
  width: 960px;
  margin: 0 auto;
}
h1{
  color: #ffff57;
  font-size: 4em;
  letter-spacing: 2px;
}
button{
  margin: 0 10px;
  background: transparent;
  border: 3px solid #ccc;
  border-radius: 20px;
  padding: 10px;
  color: #ccc;
  font-size: 1.2em;
  cursor: pointer;
}
button:hover{
  color: #fff;
  border-color: #fff;;
}
.content{
  text-align: left;
}
.card{
  padding: 8px 16px;
  background: #1b1b1b;
  margin: 16px 0;
  border-radius: 20px;
}
.card p{
  margin: 6px 0;
  color: #999;
}
.card h3{
  margin: 10px 0;
  color: #ffff57;
}
button:disabled{
  opacity: 0.2;
  cursor: not-allowed;
}

Your Result should look like this :

First Result

Making Use of Paginated Queries

It's a popular UI design to render paginated data, and React Query makes it "just work" by adding the page information in the query key and making use of usePreviousData:

You can obtain some new features when you set keepPreviousData to true:

  • Even if the query key has changed, the data from the most recent successful fetch is still accessible while new data is being fetched.

  • The old data and the new data are seamlessly switched when the new data is received.

  • If you want to know what data the query is presently returning, you can make use of {isPreviousData}.

To have a better experience of creating a paginated page in React Query you will have to make use of usePreviousData in your query.

//Planets.jsx
const [ page, setPage ] = useState(1);
const { 
     error,
     data,
     isFetching, 
    isPreviousData, 
    status 
  } = useQuery(['planets', page], ()=> fetchPlanets(page), { keepPreviousData : true });

Update your asynchronous function fetchPlanets to take in page as a parameter in the Planets.jsx file.

const fetchPlanets = async (page) => {
  const res = await axios.get(`https://swapi.dev/api/planets/?page=${page}`);
  return res.data;
}

Next, you create a “Next” button and a “Previous” button which will be making use of the values from {usePreviousData}.

In the “Previous” button page the setPage function from the useState hook is set to update the page state and set to be disabled when page === 1.

In the “Next” button the setPage is also set to update the page state and set to be disabled if there is no more data to be fetched.

After the “Next” button a span tag is created signifying whether it is still fetching or not.

//The previous button            
<button 
            onClick={() => setPage(old => Math.max(old - 1, 1))} 
                        //Set to Disabled if page === 1
            disabled={page === 1}>
            Previous Page
          </button>
          <span>{ page }</span>
                    //The next button
          <button 
            onClick={() => {
                if (!isPreviousData || data.next) {
                  setPage(old => old + 1)
                }
              }}
           // Set to Disabled if the previous Data has not been fetched
         disabled={isPreviousData || !data?.next}
            >
            Next page
          </button>
          {isFetching ? <span> Loading...</span> : null}{' '}

Planets.jsx file now looks like this

//Planets.jsx
import { useState } from 'react';
//Importing useQuery from the react-query library 
import { useQuery } from 'react-query';
import axios from "axios";
import Planet from './Planet';

//This is an asynchronous function which is used in fetching from the api 
const fetchPlanets = async (page) => {
    const res = await axios.get(`https://swapi.dev/api/planets/?page=${page}`);
    return res.data;
}

const Planets = ()=>{
  const [ page, setPage ] = useState(1);
  const { 
    error,
    data,
    isFetching, 
   isPreviousData, 
   status 
 } = useQuery(['planets', page], ()=> fetchPlanets(page), { keepPreviousData : true });

    return (
    <div>
        <h2>Planets</h2>
    {/* Signifying if it is in a loading state */}
      {status === 'loading' && (
        <div>Loading data</div>
      )}
{/* Signifying if it encounters an error on the way */}
      {status === 'error' && (
        <div>Error: {error.message}</div>
      )}
{/* Signifying if the fetching was successful*/}
      {status === 'success' && (
        <>
        //The previous button            
            <button 
            onClick={() => setPage(old => Math.max(old - 1, 1))} 
                        //Set to Disabled if page === 1
            disabled={page === 1}>
            Previous Page
          </button>
          <span>{ page }</span>
                    //The next button
          <button 
            onClick={() => {
                if (!isPreviousData || data.next) {
                  setPage(old => old + 1)
                }
              }}
           // Set to Disabled if the previous Data has not been fetched
         disabled={isPreviousData || !data?.next}
            >
            Next page
          </button>
                {/*Checking if it is still fetching or not */}
          {isFetching ? <span> Loading...</span> : null}{' '}
          <div>
            { data.results.map(planet => <Planet key={planet.name} planet={planet} /> ) }
          </div>
        </>)}
    </div>
    )

}

export default Planets

The final result now looks like this:

React Query Gif.gif

Conclusion

This tutorial has walked you through the process of fetching data and making use of pagination for performance optimization using React Query. You learned about the useQuery hook and its application.

At this point, you are confident enough to make use of React Query in fetching data into your react app. Check out the source code and live URL here.

Reference

Did you find this article valuable?

Support Quincy Oghenetejiri by becoming a sponsor. Any amount is appreciated!