snow-mountain

Handling and Managing Server State With React Query


Last updated on
ReactReact QueryWeb Development

Summary (TL;DR): In this article, we will build a “lyrics finder” app, focusing on the benefits of React Query, like fetching and caching data from an API. This tutorial will help you sharpen your server data fetching knowledge in React.

React Query is often described as the missing data-fetching library for React, but in more technical terms, it makes fetching, caching, synchronizing, and updating server state in your React applications a breeze.

source: https://react-query.tanstack.com/overview

Fetching, caching, and updating the server state in React can be tricky, but React Query give you the option to spend less time handling it.

While You Are at It, Learn More About React Dynamic Imports and How To Use Them

Table of Contents

Prerequisites
Setting Up a Development Environment
API
>> Why Choose React Query?
>> Why Axios?
>> Why Not Use Only Axios?
>> Project Overview
Using Axios To Define Our Reusable API Methods
Introduction to Fetching Data With the useQuery() Hook
Create a Search Field

Prerequisites

Foreknowledge in:

  • Data fetching from APIs
  • React
  • Axios

Setting Up a Development Environment

Create a new React app, install the following packages, and start the app:

~ npx create-react-app my-app
~ cd my app
~ npm I react-query axios
~ npm start
  • Our project UI will be opened in http://localhost:3000
  • To enable React-Query in our app we need to wrap the main app.js component with QueryClientProvider component from React Query, indicating where our data fetching would be done.

import React from 'react';
import ReactDOM from 'react-dom/client';
 
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

import { QueryClient, QueryClientProvider } from "react-query";
 
const root = ReactDOM.createRoot(document.getElementById('root'));
const queryClient = new QueryClient();

root.render(
 <QueryClientProvider client={queryClient}>
  <App />
 </QueryClientProvider>
);

API 

APIs take a huge part of our application.

For those who aren’t too familiar with APIs, it is an acronym that stands for Application Programming Interface, which is a software intermediary that allows two applications to talk to each other.

For the sake of this article, we built one, using Node.js and Express. This server contains an array of objects of nine songs and lyrics with other vital information like the release year and artists.

We will use useQuery() to fetch and manage our serve data state.

Why Choose React Query?

Are you having issues with server state management? Or, perhaps, you are a fan of writing ridiculously long code for data fetching? For these and many other reasons, React Query has been made.

React, on it’s own, lacks a standard data-fetching model when building large applications, and this has led to the emergence of several server state management libraries. However, these libraries do not fully support the proper handling of Async data.  

Async data is handled on the component where each state associated with it is usually tracked, i.e loading, error, data, etc. As the number of server states tracked increases, it becomes increasingly more difficult to manage them.

React Query is a library that helps manage server state in an exceptional way. 

Project Overview 

All we will do is fetch the data using React Query, manage the server state and filter the fetched data which enabled us to access the lyrics attached to each song.

The logic behind the build: 

  • useQuery(); we use this to fetch and cache data in our component
  • useState(); we use this to handle the value in the input
  • useEffect(); we use this to handle the filter
  • at the end of the project, we will have this:

Using Axios To Define Our Reusable API Methods

We need to define an Asynchronous function called fetchLyrics(), that function will make a network request to our local API in order to fetch data. In addition, we need to create a component that would fetch this data, in our case, Lyrics.js, in this component we’ll use the Axios.get() to fetch and we’ll export an async function so we can access and manage server data in any component using react-query:

import axios from 'axios';

export async function fetchLyrics(){
 const {data} = await axios.get('http://localhost:8081/fetchlyrics/');
   return data;
}

Fetching Data With the useQuery() Hook

The useQuery() hook is a function used to fetch data in React Query. It takes two arguments, one is a key and the other is an asynchronous function for fetching data. This hook is responsible for caching after the initial fetch, doing this ordinarily in React would be done by useState() and the useEffect() hook, and it would take much less lines of code.

Below are the steps for managing server data using the useQuery hook.

  • We create a new component named Fetch.js
  • Then, we import fetchLyrics from Lyrics and useQuery() from react-query
  • We declare the useQuery() 
  • Handle caching using isLoading and isError from react-query
  • Inside the JSX, we return the fetched data songs from our API by mapping through the array of objects in our API.
import React from 'react';
import { useQuery } from 'react-query';
import { fetchLyrics } from './Lyric';
import './App.css';

function Fetched({ searchValue }) {
  const { data, error, isError, isLoading } = useQuery('lyrics', fetchLyrics);

  if (isLoading) {
    return <div>Loading...</div>;
  }
  if (isError) {
    return <div>Error! {error.message}</div>;
  }

  return (
    <pre>
      {filteredData.map((fetchLyrics, server) => {
        return <li key={server}>{fetchLyrics.Song}</li>;
      })}
    </pre>
  );
}

export default Fetched;

We can now import this into our main  app.js component

Create a Search Field

We have been able to fetch, cache and manage server data using useQuery() from React Query, which has made our app ready in a few minutes.

The need for a search field arises as it would help us to filter the exact data needed from our API. We will use useState() and useEffect() hook to enable users to filter the exact song lyrics they need. 

First we go in the App.js component and import useState()  and set our state to an empty string, in our JSX we can return an input tag 

import React from 'react';
import { useState } from 'react';
import Fetched from './fetched';
import './App.css';

function App() {
  const [value, setValue] = useState('');

  console.log(value);

  return (
    <div className="container">
      <div className="App">
        <h1>Search for Lyrics</h1>
        <input
          type="text"
          className="inp"
          placeholder="name of song"
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
        <hr />
        <Fetched />
      </div>
    </div>
  );
}

export default App;

Since we didn’t handle data fetching in our main app.js we will pass the value as a prop to Fetched.js

<Fetched searchValue={value}/>

And now we can access the value, and we’re able to filter whatever the value is, against the fetched data.

We need to import useEffect()  and useState() in our Fetched.js component. To filter an array according to the values passed in the input our state needs to be an empty array.

const [filteredData, setFiltredData] = useState([])

Then we can use two methods, first the some() and the find(), the some() method runs tests whether at most one element in the array passes the test carried out by the provided function, then it returns a Boolean value. While the find() method is used in cases where you might want to retrieve an array value. In this project, we made use of the some() method.

import React, { useEffect, useState } from 'react';
import './App.css';
import fetchLyrics from './Lyric';
import { useQuery } from 'react-query';

function Fetched({ searchValue }) {
  const { data, error, isError, isLoading } = useQuery('lyrics', fetchLyrics);
  const [filteredData, setFiltredData] = useState([]);

  useEffect(() => {
    if (data) setFiltredData(data);
  }, [data]);

  useEffect(() => {
    const handleSearch = (data, searchValue) => {
      const filteredData = data.filter((value) => {
        const searchStr = searchValue.toLowerCase();
        const nameMatches = value.Song.toLowerCase().includes(searchStr);
        return nameMatches;
      });
      setFiltredData(filteredData);
    };

    if (data) {
      handleSearch(data, searchValue);
    }
  }, [searchValue]);

  //...
}

And some basic styling 

//STYLING
@import url("https://fonts.googleapis.com/css2?family=Fredoka+One&family=Montserrat&display=swap");

.App {
   min-height: 100vh;
   height: auto;
   text-align: center;
   font-family: "Montserrat", sans-serif;
}
 
h1 {
   font-family: "Fredoka One", cursive;
   color: #B2BEB5;
}
 
pre {
   font-size: 25px;
   color: #113a5d;
}
 
hr {
   margin-top: 22px;
   width: 61%;
}
 
.input {
   width: 202px;
   height: 41px;
   border: 2.5px solid #113a5d;
   border-radius: 10px;
   padding-left: 6px;
   font-size: 23px;
}

Lastly, we can console.log() filteredData so we can see if we are getting the correct data after filtering. If we do, then we have successfully been able to build an app with React Query if we don’t, we need to fix a few things…

You can check if our data corresponds with our API.

Conclusion 

By building a detailed application, we were able to learn about the most common React Query features: how to fetch data and manage states, this has made it easier to fetch and manage server data in less time and in few code lines. 

We hope that this approach can help you build to build React apps in a better way.