How to use React Query With Next Js , and why you should do ?
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.
Subscribe to my newsletter
Read articles from Rahim directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by