How to use React Query With Next Js , and why you should do ?

RahimRahim
9 min read

What is React Query

React query is an open-source library that helps you with fetching data, cashing , prefetching data and a bunch more methods, it makes your code clean and helps you to write less code and do more

How the data is fetched without ussing React Query

If you want to fetch data in your client components without using React Query , you will usually write the following code

"use client"

import { useEffect, useState } from "react"
import axios from  "axios"

export const   CityInfo = ()=>{
    const [cityData, setCityData] = useState(null)

const getData = async  ()=>{
  try{
    const {data} = await  axios.get("https://en.wikipedia.org/api/rest_v1/page/summary/Paris")
    setCityData(data)
      }
      catch(err){
        console.error(err)
      }

}
useEffect( ()=>{
  getData()
} , [] )
return <div>
  paris city
</div>
}
  • First, you have to define a function called getData then you have to handle errors using "try and catch", and you also have to use the "useEffect" hook to trigger the getData function when the component is rendered, These makes your code messier and hard to read and manage

  • In order to avoid writing this Spaghetti code, we should use React Query, and in this post, you will learn to use it to write clean code

How to Use React Query

Installing the @tanstack/react-query dependency

First, you should install the dependency by running: npm i @tanstack/react-query or yard add @tanstack/react-query

npm i @tanstack/react-query

Initializing the client provider

After installing the dependency, you have to wrap your Next JS app in a "QueryClientProvider" that you get from the "@tanstack/react-query" dependency, then you have to include the client object in the client provider as shown bellow :

"use client"
import { QueryClientProvider , QueryClient } from "@tanstack/react-query"

const queryClient = new QueryClient()

export const ReactQueryProvider : React.FC<{children : React.ReactNode}>=({children}  )=>{
    return <QueryClientProvider client={queryClient} >
        {children}
        </QueryClientProvider>
}

First, you have to create a file in the 'providers' folder for the 'QueryClientProvider', in that provider you have to include the 'queryClient' object as you can see on the above code, and then you have to wrap your entire app inside this provider so you can get access to the 'queryClient' object in your entire app

Make sure that you define the 'queryClient' variable outside the ReactQueryProvider component in order not to run into issues

In your layout file, you have to wrap your entire app in the ReactQueryProvider as you can see the the code below:

import { ReactQueryProvider } from '@/providers/react-query'
import './globals.css'

export default  function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
    return (
    <html lang="en">
      <body>
        <ReactQueryProvider>
        {children}
        </ReactQueryProvider>
        </body>
    </html>
  )
}

Fetching Data With React Query

In react query, there are two types of requests, the get or delete request which does not include a body, and a post or patch request which supports a body object

  • In order to make a post or a patch request you have to use the "useMutation" hook

  • In order to make a get or a delete request, you have to use the "useQuery" hook,

The useQuery hook

"use client"
import axios from  "axios"
import { useQuery } from "@tanstack/react-query"


export const   CityInfo = ()=>{
 const {data , error  , refetch  , isError , isLoading}  = useQuery({
    queryKey : ["Paris"]  ,
    queryFn : async  ()=>{
    const response = await  axios.get("https://en.wikipedia.org/api/rest_v1/page/summary/Paris") 
    return response.data
 } , 
})

 if(isError) return <div>we got this error : {JSON.stringify(error)}</div>
 else  if(isLoading) return <div>loading...</div>
return <div>
    {JSON.stringify(data)}
</div>
}

In the above example, we are using the useQuery hook to get the data from a Wikipedia API

the "useQuery" hook automatically fetches the data when the comment renders and it returns all the necessary things that we need such as the data that we get from the server, errors if we have ... etc

We can get all the data that we need without having to use "useEffect" and 'useState' hooks, and without having to use "try" and "catch" to handle errors

The "useQuery" hook accepts the following arguments :

  • "queryKey" : is an array of strings used for cashing purposes, The hook will refetch the data when any of these values are changed

  • "queryFn": is a function that we use to fetch the data from an API, and we have to return the data that we are getting from the API in the "queryFn", It accepts an object (as an argument) that includes valuable data such as the query keys

  • enabled: It is a boolean, If the enabled is false React query will not fetch the data until it becomes true, One common use case of the enabled property is when you want to run the fetch function only when you have some specific data

  • "refetchInterval": It is a number in milliseconds, for example, if the 'refetchInterval' is 1000, react query will re-fetch the data from the API every 1 second

  • "onSuccess" (optional): is a function that runs when we successfully fetch the data, it accepts the data returned from the "queryFn" as an argument

  • "onError" (optional): a function that runs when we get an error while fetching the data, it accepts the error as an argument

You can read more about these arguments on the Tanstack Query documentation

The "useQuery" hook returns the following data :

  • "data": which is the actual data that we return from the "queryFn" function

  • "status": It is a string that describes the status of our request, It will be "loading" when the fetching request is in progress, "error" when we got an error and "success" when we successfully get the data from the api

  • "isLoading": a boolean that indicates whether we are currently requesting the data or not

  • "isError": a boolean that indicates whether we got an error or not

  • "error": This will be the error object if we get an error from the API, if we do not get an error from the API, the error will be null

  • "refetch" , a function that we can call when we want to re-fetch the data

This is how the useQuery hook looks like with all the previous inputs and outputs

 const {data , error  , status , refetch  , isError , isLoading}  = useQuery({
    queryFn : async  ()=>{
        const response = await  axios.get("https://en.wikipedia.org/api/rest_v1/page/summary/Paris") 
        return response.data
     }  ,    
    queryKey : ["Paris"]  ,

    refetchInterval : 1000 , // refetch the data every 1 second
    enabled : true, 
    onSuccess : (data)=>{
// do something when we get the data
    } , 
    onError : (error)=>{
// do something when we get an error 
    }
})

You can read more about these on the Tanstack query official docs

Now you can compare the last code with the first code to see how React Query is making your code clean and easy to write and read

The Use Mutation Hook

If you want to make a post or a patch request, you have to use the "useMutation" hook

The following code represents a simple example of how we can use the "useMutation" hook :

"use client"
import axios from  "axios"
import { useQuery , useMutation } from "@tanstack/react-query"
import {useState} from "react"

export const   CityInfo = ()=>{
const [cityValue, setCityValue] = useState("")

const { mutate , data , isLoading , isError , error } = useMutation({
    mutationFn :  async ()=>{
const responce = await  axios.post("/api/setCity " , { city : cityValue } )
return responce.data
    } ,
    onSuccess : (data)=>{
        // do something when we get the data
    }, 
    onError : (err)=>{
        // do something when we get an error
    }
})

const handleSubmit = (e)=>{
e.preventDefault()
mutate()
}

return <div>
<form onSubmit={handleSubmit} >
<input value={cityValue} onChange={(e)=>setCityValue(e.target.value)}  />
<button type="submit" >add city</button>
</form>
</div>
}

Unlike the useQuery hook that automatically fetches the data , we have to call the mutate function that we are getting from the "useMutation" hook to post the data to the API, in our example, we are calling the mutate() function in the handleSubmit function

the "useMutation" hook accepts the following properties:

  • "mutationFn": a function like the "queryFn" that sends a post or a patch request to our API and returns the data

  • "onSuccess": a function to execute when the "mutationFn" successfully posts the data to the API and gets a successful response

  • "onError": a function to execute when the mutationFn gets a response error from the API

  • "retry": a number that represents how many times to resend the request when we get an error from the API

  • "onSettled": a function to execute when the request ends, It's useful for performing cleanup or other actions that should occur regardless of the mutation's outcome.

  • "onMutate" : function that is called before the mutation function is executed

The "useMutation" hook returns the following properties:

  • "mutate": it is a function that we can call when we want to post the data to our API

  • "data": represents the data that we got as a response from our API,

  • "isLoading": a boolean that indicates whether the post request is currently in progress

  • "isError": a boolean that indicates whether we get an error from the API

  • "error": represents the error object if we get an error from the API

  • "reset": a function to reset the mutation state to the initial state, this can be useful when we want to clear an error that we got from the API

The following code represents the "useMutation" hook with all the above properties:

const { mutate  , reset  , data , isLoading , isError , error  } = useMutation({
    mutationFn :  async ()=>{
const responce= await  axios.post("/api/setCity " , { city : cityValue } )
return responce.data
    } ,
    onSuccess : (data)=>{
        // do something when we get the data
    }, 
    retry: 3, // retry 3 times before we display an error ,
    onError : (err)=>{
        // do something when we get an error
    } ,
    onSettled : ()=>{
        // do something when the request ends
    } ,
    onMutate : ()=>{
     // do something before executing the  mutationFn 
    } , 
})

Note that we can pass arguments to the mutationFn when we call the mutate() function as shown below:

const { mutate    } = useMutation({
    mutationFn :  async (body  :{city : string})=>{
const responce= await  axios.post("/api/setCity " , body )
return responce.data
    } ,
})

const handleSubmit = (e : any)=>{
e.preventDefault()
mutate({city :"paris"})
}

Now I want you to see how the "useMutation" hook is helping you as a developer to handle errors, loading , retry ... etc. when making a post or a patch request

conclusion

In conclusion, React Query is a powerful library that simplifies and enhances data fetching and state management in React applications. Throughout this article, we've explored two of its core hooks, useQuery and useMutation, and how they enable us to build robust and efficient data-driven interfaces.

0
Subscribe to my newsletter

Read articles from Rahim directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rahim
Rahim