What is React Query?
Since React is the UI Library there are no specific patterns for data fetching. We use useEffect
for data fetching and useState
for data storing

If we need to use data through the application using a State management library like Redux or context APIs.
Let Start, Install React Query from the following command.
npm i react-query
In your App.js file import the QueryClientProvider
and QueryClient
.
import { QueryClientProvider , QueryClient } from 'react-query';
Then wrap your app JSX with <QueryClientProvider> and create the instance of QueryClient. And pass it to <QueryCLientProvider> to access your complete application.
function App() {
//creating the instance of QueryClient
const queryClient = new QueryClient()
return (
<QueryClientProvider client={queryClient}>
<div className="App"></div>
</QueryClientProvider>
);
}
export default App;
ReactQueryDevTool:
React query provides the building dev tool to track all the queries. Import this from react-query and use it in your App.js to access this dev tool in all files
import { QueryClientProvider , QueryClient } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
function App() {
//creating the instance of QueryClient
const queryClient = new QueryClient()
return (
<QueryClientProvider client={queryClient}>
<div className="App"></div>
// initialIsOpen is like props which tells not to open devTool when the app start
// position : where the devTool Icon shown
<ReactQueryDevtools initialIsOpen ={false} position={'bottom-right'} />
</QueryClientProvider>
);
}
export default App;
QueryCache
Every query result is stored for 5 minutes and the next query relies on this cached for subsequent queries. The network checks if the cached and if he found the cached data then is key “isLoading” will remain false. but “isFetching” will change every time to check whether the data is changed or not then it will automatically update the view as well as cached data.
UseQuery
useQuery requires at least two arguments
- Unique Key
- The function which returns the promise
useQuery("super-heroes", () => { return axios.get("http://localhost:4000/superheroes") });
3. This hook returns multiple things we can destructure when we need them.
We can reduce the UseQuery function by creating the separate function
const fetchData = () => {
return axios.get("http://localhost:4000/superheroes");
};
1. isLoading → data is a load or not. it is Boolean
2. data is the actual data from the API
3. error and IsError → isError is the boolean value and error has the error which is occurred
4. isFetching → isFetching is the boolean value that tells if the data is again fetched or not to check whether the data is changed or not while checking the data from the cache
const { isLoading, data, error, isError, isFetching, refetch } = useQuery("super-heroes", fetchData)
Cached Time
This is the 3rd argument to change the cached time default time is 5min but you can change it according to your need. we can use to reduce the network request when the data is not changed often.
{ cacheTime: 5000 }
StaleTime
This is used to change or reduce the network request and the time indicates to pause the request for some time. this time indicates that the new network is not triggered in this time frame. This time is in milliseconds.
The default time is 0s which means on every change or every page change the network is triggered we can increase the staleTime which helps to track either pause the request for sometime
{ staleTime: 30000 }
RefetchOnMount
when set to true every time when you navigate to this component it will trigger the network request.
when set to False it will just send the request when the component did mount ( refresh )
{ refetchOnMount : true }
RefetchOnWindowFocus
When the window got focus it automatically fetches data but while working with react it will not auto-fetch. The default value is set to “true” but we can change it if we won't fetch it every time the screen got focused.
{ refetchOnWindowFocus : true }
RefetchInterval
Refetch data on a specific time interval which is mentioned in milliseconds this is also called “polling”
{ refetchInterval : 2000 }
enabled
if we want to fetch data on-click then we off the auto fetching. For this, we need to set “false”. Also, we can destruct the “refetch” property from useQuery and pass it to our onClick event.
const { refetch } = useQuery("key" , "function" , { enabled : false } )
onClick={()=> refetch}
select
we can transform our data into different types by using the “select” keyword. This’ll update the “data” key of useQuery
select: (data) => {
const superHeroName = data.data.map((item) => item.name);
return superHeroName;
}
This will make the “isLoading” state false and stop to render the loading text again and again. This provides the initial data for the query. for this, we also need to import the QueryClient and create the instance to access the cached data.
This increases the data viewing experience of the user. we can use this to fetch the API data from the response of other APIs and provide the initial data to the API
For Example, The listing page may not have the complete data for the detail page but sufficient data to fetch the details for the detail page here we can use this. This initial data can be used to avoid the loader
Steps:
import useQueryClient and create the instance which helps us to get the cached data
const fetchSuperHero = ({ queryKey }) => {
const heroId = queryKey[1]
return axios.get(`http://localhost:4000/superheroes/${heroId}`)
}
export const useSuperHeroData = heroId => {
const queryClient = useQueryClient()
return useQuery(['super-hero', heroId], fetchSuperHero, {
initialData: () => {
//'super-hero' is the key of query "data" points the array in whih we have the responce
const hero = queryClient.getQueryData('super-heroes')?.data?.find(hero => hero.id === parseInt(heroId))
if (hero) {
return { data: hero }
} else {
return undefined
}
}
set the callback onSuccess and onError
{
onSuccess,
onError
}
Custom Hook
create the “hooks” folder and create the useSuperHeroData.js and create your custom hook.
import { useQuery} from 'react-query'
import axios from 'axios'
const fetchSuperHero = ({ queryKey }) => {
const heroId = queryKey[1]
return axios.get(`http://localhost:4000/superheroes/${heroId}`)
}
//we can also pass the array in the queryKey
export const useSuperHeroData = heroId => {
return useQuery(['super-hero', heroId], fetchSuperHero )
}
Import this hook where you need
import { useSuperHeroData } from "../hooks/useSuperHeroDetail";
//we can destructure the keys from our hook also
const { data, isLoading, isFetching, isError, error } = useSuperHeroData(id);
QueryById
This can be used when we want to fetch data by some id. if we want to pass any arguments to fetched function we need to make it an anonymous function otherwise it will not call the function by just passing the traditional way of passing the argument in the function.
const fetchSuperHero = (heroId) => {
return axios.get(`http://localhost:4000/superheroes/${heroId}`)
}
---------- NOT THE CORRECT WAY ------------------
export const useSuperHeroData = heroId => {
return useQuery('super-hero', fetchSuperHero(id) )
}
---------- CORRECT WAY ------------------
export const useSuperHeroData = heroId => {
return useQuery('super-hero', () => fetchSuperHero(id) )
}
Without using the anonymous function pass the Id
const fetchSuperHero = ({ queryKey }) => {
const heroId = queryKey[1]
return axios.get(`http://localhost:4000/superheroes/${heroId}`)
}
export const useSuperHeroData = heroId => {
return useQuery(['super-hero', heroId], fetchSuperHero)
}
Parallel Queries
In dynamic queries, we can make allies of every key which we can restructure from useQuery().
const { data: SuperHeroData, isLoading: isLoadingSuperHero } = useQuery("fetchHero", fetchHero );
const { data: FriendData, isLoading: isLoadingFriend } = useQuery("fetchUser", fetchUser );
Dynamic Parallel Queries
When we have to load data of multiple users or we want to pass multiple arguments like getting the data of 5 different users one by one but we pass the id at the same time or we want the details of multiple books by passing their titles then Parallel comes into the picture.
We are receiving the array of “heroIds” using props and we are calling functions by each id individually
To achieve this we can replace our useQuery
→ useQueries
The useQueries
hook accepts an array with query option objects identical to the useQuery
hook.
const results = useQueries([
{ queryKey: ['post', 1], queryFn: fetchPost },
{ queryKey: ['post', 2], queryFn: fetchPost },
])
const qurriesData = useQueries(
heroIds.map((id) => {
return {
queryKey: ["Super-Hero", id],
queryFn: () => fetchData(id),
};
})
);
Dependent Queries
Sequential queries depend on the result of other queries
const fetchDataFromPropsEmail = (email) => {
return axios.get(`http://localhost:4000/user/${email}`);
};
const fetchDataByUserEmail = (id) => {
return axios.get(`http://localhost:4000/userData/${id}`);
};
const DependentQurries = ({ email }) => {
const { data: user } = useQuery(["user", email], () => fetchDataFromPropsEmail(email) );
const id = user?.data.id;
// now this qurries is dependent to the result of above querry
const { data: userDetail } = useQuery(["userDetail", id],() => fetchDataByUserEmail(id),
{
//this is used to fetch data automatically
// "!!" means convert tha string into boolean
enabled: !!id
}
);
Paginated Queries
This is also used in UseQuery to provide the pagination feature also. This key is used to keep showing the previous until the new data is shown to the user.
{ keepPreviousData : true }
const { isLoading , isError , error , data } = useQuery(["colors" , pageNumber] , fetchColors , {
keepPreviousData : true
} )
Infinite Queries
To implement this we need to replace the useQuery
→ useInfinateQurries
“hasNextPage” → boolean which tells whether all the pages are ended or not.
“fetchNextPage” → function which is used to fetch the next page data.
“isFetchingNextPage” → boolean which is used to show the loaded while fetching the next page
“getNextPageParams” → function which returns the next page number depending on the logic you implement it
“_” before the last page tells that we don't need this param so we just pass add the underscore with it.
Important like other queries it doesn't return data in “data.data” it returns in “data.pages”
The fetcher function automatically receives the “pageParam” from the “getNextPageParams” function we just need to accept it from the fetcher function
const fetchColors = ({pageParam = 1}) => {
return axios.get(`http://localhost:4000/colors?_limit=2&_page=${pageParam}`)
}
const { isLoading , isError , error , data , isFetchingNextPage , isFetching , hasNextPage, fetchNextPage} = useInfiniteQuery(["colors"] , fetchColors, {
getNextPageParam : (_lastPage , pages) => {
if(pages.length < 4){
return pages.length + 1
} else{
return undefined
}
}
} )
// Button logic is like this
<button onClick={() => fetchNextPage()} disabled={!hasNextPage}>
Load more
</button>
Mutations:
It is used to update, delete and post requests. useMutation
is the hook. And it doesn't need a unique key like useQuery.
This hook returns the function mutate
. first argument of this hook is the function then we can use its other function like
onSuccess → which is triggered when the mutation
is successfully done
onError → which is triggered when the mutation
returns some error.
onMutate -> this will trigger before the useMutation
function
const { mutate } = useMutation( () =>
request({ url: "/list", method: "post", data: addHero }),
);
mutate(addHero);
Query Invalidation
Automatically fetch the data when a successful mutation has occurred. used with the queryClient. we can use this onSuccess
or onSettled
or else depending on your condition.
queryClient.invalidateQueries('super-heroes')
onSuccess : (data) => {
queryClient.invalidateQueries('super-heroes')
}
onSettled: () => {
queryClient.invalidateQueries("super-heroes");
},
Handling Mutation Response
“setQueryData” and “getQueryData” is used to handle the mutation data. when we are getting the query data we need to store the data in some variable
const previousData = queryClient.getQueryData("super-heroes");
but in the case of setting, we can just set the data
queryClient.setQueryData("super-heroes", context.previousData);
Cancel Queries
we can use this if we do not want to auto-fetch some queries that interface with our custom logic so we cancel that queries to avoid auto-fetching.
queryClient.cancelQueries("super-heroes");
Optimistic Update
When we assume that our mutation query is done without any error and we are sure about our result of success then we use this query. This is used to enhance the user experience. For this, we need 3 callbacks
onMutate()=>{}
onError() => {}
onSettleed() => {}
onMutate()
is called before the mutation function is fired and it receives the same variable(data, object) the mutation function receives.
Before updating the optimistic update we must need to store the old data in case to roll back if our mutation fails.