React query is a really powerful library to work with asynchronous data in React. It is termed as the missing data-fetching library in React

While most traditional state management libraries are amazing at client side state management, the complex problems of the server state need to be dealt with.

This is because server state is totally different. For starters, server state:

  • Is persisted remotely in a location you do not control or own
  • Requires asynchronous APIs for fetching and updating
  • Implies shared ownership and can be changed by other people without your knowledge
  • Can potentially become "out of date" in your applications if you're not careful

api-meme

Once you grasp the nature of server state in your application, even more challenges will arise as you go, for example:

  • Caching
  • Deduping multiple requests for the same data into a single request
  • Updating out of date data in the background
  • Knowing when data is "out of date"
  • Reflecting updates to data as quickly as possible
  • Performance optimizations like pagination and lazy loading data
  • Managing memory and garbage collection of server state
  • Memoizing query results with structural sharing

Much of theory though, let's get rolling by creating a simple react component using react query to fetch some data from a server

 import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
 
 const queryClient = new QueryClient()
 
 export default function ParentComponent() {
   return (
     <QueryClientProvider client={queryClient}>
       <ChildComponent />
     </QueryClientProvider>
   )
 }
 
const fetchData = async() => {
 const API_ENDPOINT ='https://api.github.com/repos/tannerlinsley/react-query'
 try{
       const res = await fetch(API_ENDPOINT)
       return res.json
 }catch(error => console.log(error))
}
 
 function ChildComponent() {
   const { isLoading, error, data } = useQuery('repoData', fetchData)
 
   if (isLoading) return 'Loading...'
 
   if (error) return 'An error has occurred: ' + error.message
 
   return (
     <div>
       <h1>{data.name}</h1>
       <p>{data.description}</p>
       <strong>👀 {data.subscribers_count}</strong>{' '}
       <strong>✨ {data.stargazers_count}</strong>{' '}
       <strong>🍴 {data.forks_count}</strong>
     </div>
   )
 }
scr
react query simple application

Basically we just need to do 3 things :

  1. Setup a query client : This is done at the parent component to ensure all the children can easily use the useQuery hook available and track and optimize their async API calls from the frontend.

Easily implemented in 2 steps:

i) Setup a client using the QueryClient constructor

 import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
 
 const queryClient = new QueryClient()

ii) Pass it down as context to all child components using a QueryClientProvider

 export default function ParentComponent() {
   return (
     <QueryClientProvider client={queryClient}>
       <ChildComponent />
     </QueryClientProvider>
   )
 }

2. Define a function that makes this call

const fetchData = async(key) => {
 const [type, greeting, page] = key.queryKey;
 console.log(greeting)
 const API_ENDPOINT =`https://swapi.dev/api/people/?page=${page}`
 try{
       const res = await fetch(API_ENDPOINT)
       return res.json
 }catch(error => console.log(error))
}

2. useQuery hook : Use this react hook to fetch, configure and optimize everything about your async API calls.

const [page, setPage] = useState(1)
    const { data, status } = useQuery(['people', 'hello friends', page], fetchPeople, {
        staleTime: 5000,
        // cacheTime: 10,
        onSuccess: () => console.log('data fetched with no problemo')
    })

Basically in steps 2 and 3, we have also carefully passed parameters to the useQuery hook and used them in our API call function. This is yet another cool feature that the useQuery hook offers


Now, combining it all in a ChildComponent we can show this on the UI

import React,{useState} from 'react'
import {useQuery} from 'react-query'
import Person from './Person'

const fetchPeople = async (key) =>
{
    const [type, greeting, page] = key.queryKey;
    console.log(greeting)
    const res = await fetch(`https://swapi.dev/api/people/?page=${page}`)
    return res.json()
}

const People = () =>
{
    const [page, setPage] = useState(1)
    const { data, status } = useQuery(['people', 'hello bois', page], fetchPeople, {
        staleTime: 5000,
        // cacheTime: 10,
        onSuccess: () => console.log('data fetched with no problemo')
    })
    switch(status)
        {
            case 'error':
            {
                return (<p>Error fetching from API</p>)
                break
            }
            case 'loading':
                {
                    return ( <p>Loading....</p> )
                    break
                }
            case 'success':
            {
                return (
                    <>
                    <button onClick ={()=>setPage(1)} >Page 1</button>
                    <button onClick ={()=>setPage(2)} >Page 2</button>
                    <button onClick ={()=>setPage(3)} >Page 3</button>
                    {data.results.map(person => (
                    <div className="container">
                        <h3>{person.name} </h3>
                        <p>Gender : {person.gender} </p>
                        <p>Birth year : {person.birth_year} </p>
                    </div>
                    ) )}
                    </>
                )
            }
        }
}

export default People

React Query Dev Tools

A collection of highly powerful and insightful tools that help visualize all of the inner workings of React Query and will likely save you hours of debugging if you find yourself in a pinch!

You can easily integrate them into your project with just 2 lines of code in the parent file !!

import {QueryClient, QueryClientProvider, useQuery} from 'react-query'
import Planets from "@components/Planets"
import People from '@components/People'
import {ReactQueryDevtools} from 'react-query-devtools'

const queryClient = new QueryClient()

export default function Home({page})
{
  return (
    <>
    <QueryClientProvider client={queryClient}>
    <div>
    {page == 'planets' ? <Planets/> : <People/> }
    </div>
    </QueryClientProvider>
    <ReactQueryDevtools/>
    </>
  )
}

dev-tools

Conclusion

This is how async API call management can be effectively handled from a React JS frontend. I hope you have learnt something of value and wish you the very best of luck on your developer journey.

Additional Resources

  1. For better insight on this topic you can follow this amazing tutorial series (on a deprecated version of this package, but nevertheless brilliantly well-taught) :
React Query Tutorial
Share your videos with friends, family, and the world

2. You are feel free to browse through the code of my integration of React Query in a Next JS frontend

crew-guy/react-query
A repo to study and understand using react query to handle, optimize and monitor async API calls from a React JS frontend - crew-guy/react-query