Master Caching in React Query for Better Web App Performance
MAR 12, 2025

MAR 12, 2025
Hello Devs,
When you are working with React, fetching data from an API or other external sources can be a hassle. This is true when you need to manage data, keep it up-to-date, and ensure your app doesn’t make unnecessary API calls. Enter React Query — a library that simplifies this whole process. It makes it easier to manage API calls in React Query by automating caching, background fetching, and synchronizing data.
React Query (TanStack Query) has become an essential tool for data fetching in our applications. We frequently rely on React Query hooks, such as “useQuery” and “useMutation”, along with the fetching state ( “isLoading”, “isPending”, “isFetching” and “isSuccess”, etc.) values it provides. The useQuery hook is one of the React Query hooks that help you fetch, cache, and synchronize your data effortlessly.
Are you truly tapping into the full potential of React Query? In this discussion, we’ll explore some of its lesser-known features of React Query and demonstrate how to make the most of React Query for more efficient and effective data management.
In this blog, we’ll dive into the function and advantages of “useQuery,” “queryKey,” “gcTime,” “staleTime”, and “refetch()”.Our expert at Webelight Solutions Pvt. Ltd. will show how you can harness React Query caching features to optimise your web application, making it more efficient and reducing unnecessary complexity. For a more detailed walkthrough, check out our React Query caching tutorial.
The “useQuery” hook is a part of the React Query (now called TanStack Query) library, which can help you fetch data from external sources like APIs. Unlike traditional methods like “fetch” or “axios”, “useQuery” offers built-in features that take care of much of the heavy lifting for you. With “useQuery”, you get caching, automatic re-fetching, and background synchronization of your data, all out-of-the-box.
a) Fetching Data: “useQuery” lets you specify a query function that fetches the needed data. This could be an API call or any other asynchronous data-fetching method.
b) Caching: React Query automatically stores the data in a local cache once the data is fetched. If you request the same data again, you don’t have to make another API call — React Query will simply return the cached result. This can significantly reduce your app's API requests, improving performance and efficiency.
c) Automatic Background Refetching: What happens when the cached data becomes stale? React Query automatically fetches fresh data in the background if the cached data is outdated. This process occurs without interrupting the user’s experience, so your app always uses the latest data.
d) Stale Time: Every cached piece of data has an associated stale time. This is the period during which the data is considered fresh. After that, React Query will automatically re-fetch the data to ensure it stays current. This is set to 5 minutes by default, but you can adjust this as needed.
One crucial feature is how React Query allows you to manage API calls in React Query more efficiently, minimizing redundant requests and making data fetching seamless with its built-in caching and synchronization features. The main benefit of using “useQuery” in React applications is that it abstracts away many common challenges of working with server-side data:
a) Simplifies Data Fetching: “useQuery” handles the fetching process, so you don’t need to manually make HTTP requests, handle loading states, or manage errors in every component.
b) Caching Reduces API Calls: Thanks to automatic caching, React Query only requests data when necessary, reducing unnecessary API calls and improving your app's performance.
c) Keeps Data Fresh: The combination of React Query caching and background refetching ensures that your app always displays the latest data without making the user wait.
d) Seamless Integration with React: As a hook created for React, “useQuery” fits naturally into your software application’s component lifecycle. It works declaratively, enabling you to integrate it within your React components without complex state management.
So basically, the “useQuery” hook fetches data from a GET API (an API that uses the GET method). It takes an object as a parameter with two essential properties: “queryKey” and “queryFn”. While these two are sufficient to make API calls, our goal goes beyond merely making API calls, right?
We all know that React Query caches the data/response received from an API. However, the same API is often called multiple times with different parameters and payloads. So, how does React Query manage caching efficiently? How does it store data in an organised manner and retrieve it effectively? Let’s understand it.
You may wonder, what is queryKey? In simple terms, “queryKey” is like a unique identifier or name tag for your cached data. The React Query optimization process helps you store and manage your API data efficiently. Think of it as giving each piece of data its name so you can find it easily in your cache later.
“QueryKey” is an array that can be as basic as a string inside the array or more complicated with multiple strings and nested objects. It can be structured however you want, but it must be unique for the specific data you're caching in React Query. If your data changes based on certain variables (like a user ID or search term), these variables should be included in the “queryKey” to ensure React Query can properly cache and “refetch” data when needed.
When using “useQuery”, it’s essential to include all dependent parameters in the “queryKey” array, as shown below:
// sample payload values for below queryKey examples
const productId = "1828";
const page = 1;
const pageSize = 10;
const searchText = "tshirt";
const sortType = "desc";
queryKey: [“account-details”]
queryKey: [“product-details”, productId]
queryKey: [“products-list”, page, pageSize, searchText, sortType]
The below examples showcase 3 different ways to structure a “queryKey”:
a) A single static string [“account-details”] for data that remains relatively constant.
b) A string with a dynamic value [“product-details”, productId] to make sure each product’s details are cached separately.
c) A combination of multiple dynamic values [“products-list”, page, pageSize, searchText, sortType], to ensure the cache reflects specific pagination, filters, and sorting settings, allowing for more accurate and efficient data retrieval.
Carefully structuring the “queryKey” allows you to control caching in React Query, minimize redundant API requests, and enhance your application's performance.
a) Caching: One of the main jobs of “queryKey” is to help React Query cache your data. It ensures you don’t repeatedly request the same data from the backend. This boosts performance and saves unnecessary network requests.
b) Dependability: The “queryKey” is not just a name. It acts as a dependency for data fetching. If any part of the “queryKey” changes—say, the productId changes—React Query will know it needs to fetch the latest data from the backend.
c) Organization: Using structured “queryKey” naming is also a good practice for scalability and debugging. Giving meaningful names to your data (like users/1/posts for a specific user’s posts) makes tracking and managing in React Query dev tools much more manageable. This organization is invaluable when you have a lot of queries in your app.
Now, you might be curious—what is gcTime? “gcTime” (Garbage Collection Time) refers to the millisecond duration that cached data remains available in memory after it has become inactive. In other words, it determines how long React Query keeps the cached data before considering it unused and eligible for garbage collection. By default, the “gcTime” is set to 5 minutes (300,000 milliseconds), which can be customized based on the application's needs.
When data is fetched and stored in the cache, it remains in memory even after the component that initially requested the data is unmounted or no longer actively used. However, React Query has mechanisms to manage memory efficiently by clearing out unused data once a certain amount of time has passed without any request or reference to that data.
Setting a higher “gcTime” means the cached data will stay in memory longer, even if it is not actively used. This is useful for situations where you have data that may not be frequently accessed but is vital to keep readily available.
For example, if you have a user profile or account information that doesn’t change often but should be kept in memory for the user’s session, setting a higher “gcTime” ensures that the data is available without needing to “refetch” it from the server.
On the other hand, lowering “gcTime” helps improve memory efficiency, especially in web applications where data is frequently updated or doesn't need to persist long after being retrieved. By reducing the time that data remains in memory, you can ensure that only the most relevant and frequently used data is cached, preventing memory from being overused.
useQuery(["user-profile"], fetchUserProfile, {
gcTime: 1000 * 60 * 10 // Data will be garbage collected after 10 minutes
});
This means the cached user profile data will remain stored for 10 minutes after it’s no longer used. By modifying the “gcTime” setting, you can find a balance between optimizing memory usage and the chances of needing to fetch fresh data from the server.
Lastly, you might ask, what is staleTime? “staleTime” in React Query defines the duration for which the fetched data is considered "fresh" or up-to-date. It controls how long data stays in a valid state before React Query considers it "stale." Once data becomes stale, React Query will trigger a “refetch” the next time the component requests the data or when the query is triggered again.
By default, “staleTime” is set to 0 milliseconds, meaning that the data is immediately marked as stale after it's fetched. This results in a “refetch” occurring whenever the data is requested again, even if it's just a re-mounting of the component. This behaviour ensures that fresh data is always fetched, but it can lead to unnecessary network requests, which may affect performance.
Configuring “staleTime” can help optimize performance by preventing unnecessary refetches. For example, if the data doesn't change frequently or doesn’t need to be updated each time the component is mounted, setting a longer “staleTime” reduces redundant network requests, which can lead to a smoother and more responsive experience for the user.
useQuery([“user-profile”], fetchUserProfile, {
staleTime: 1000 * 60 * 5 // Data remains fresh for 5 minutes
});
For instance, if you set the “staleTime” to 5 minutes (300,000 milliseconds), React Query will mark the fetched data as fresh for that period. During this time, if the same data is requested (e.g., by revisiting a page or re-mounting the component), no new network request will be triggered. This ensures that the user won't experience unnecessary delays in loading the data, improving overall performance and reducing load on the server.
a) Reducing Unnecessary Refetching: By increasing “staleTime”, you reduce the frequency of network requests, leading to fewer redundant API calls and improved performance.
b) Time-Based Caching: “staleTime” allows for a more flexible approach to data caching based on how often the data changes. For data that doesn't change frequently, a longer “staleTime” can be set, while for frequently updated data, a shorter “staleTime” may be more appropriate.
c) Relation with “gcTime”: It’s important to note that “staleTime” should always be less than or equal to “gcTime”. This ensures that React Query optimization doesn't accidentally clear cached data before it is considered stale, preventing unnecessary refetching or memory overhead.
Now that we understand “queryKey”, “gcTime”, and “staleTime’, let’s look at a real-world example of fetching user data efficiently.
import { useQuery } from "@tanstack/react-query";
const oneHour = 1000 * 60 * 60;
export const useGetUserData = () => {
const { data, isLoading, isError, refetch } = useQuery({
queryKey: ["get-user-data"],
queryFn: async() => await fetch("https://www.example.com/api/v1/auth/user-data"),
gcTime: oneHour,
staleTime: oneHour,
});
return {
userData: data,
isUserDataLoading: isLoading,
isError: isError,
getUserData: refetch
};
};
import React from "react";
import { useGetUserData } from "./useGetUserData";
import { Loader } from "./loader";
const UserDetails = () => {
const { userData, isUserDataLoading } = useGetUserData();
return (
<div className="flex items-center justify-center h-screen bg-gray-100">
{isUserDataLoading ? (
<Loader />
) : (
<h6 className="text-3xl font-bold text-gray-800">{userData.name}</h6>
)}
</div>
);
};
export default UserDetails;
a) The user data will remain cached for an hour before being considered for garbage collection.
b) The data stays fresh for an hour, reducing the need for unnecessary refetching.
c) If multiple components use this hook during that hour, they will access the cached data instead of making redundant API calls.
d) React Query handles caching with the “queryKey ["get-user-data"]”, eliminating the need for additional state management tools like Redux or Context.
We can enhance performance and reduce unnecessary network requests by utilizing React Query caching and state management features of React Query. If you want to learn more about efficient caching strategies, the React Query caching tutorial is a great resource to dive deeper into best practices.
1) What Happens After 1 Hour?
Once the cache expires after one hour, React Query will perform a real network request instead of pulling data from the cache. This guarantees that the most up-to-date data is fetched and stored in the cache, initiating a new one-hour cycle.
2) What if Fresh Data is Needed, Such as After Updating the User Profile?
Let’s explore that further below:
At times, we need to fetch fresh data before the cache expires, such as after updating a user's profile. React Query offers a “refetch” method that triggers an immediate network request, updating the cached data.
const { userData, getUserData } = useGetUserData();
const getFreshUserData = () => {
getUserData(); // Force fresh data fetch
};
In the hook above, we return “getUserData”, which includes the “refetch” method from “useQuery”. By calling “getUserData()”, React Query bypasses the cache and requests a new network to fetch the most recent data. This ensures the user always gets the latest information, even if cached data is still available.
The next time you fetch data with React Query, ensure you set the “queryKey” and specify appropriate time intervals for “gcTime” and “staleTime” based on your needs. This will help cache the data, minimize network requests, reduce reliance on external state management systems, simplify your code, and make your app faster and more efficient.
If you have any questions or suggestions, feel free to reach out to us. Happy coding!
At Webelight Solutions Pvt. Ltd., our development team specializes in building scalable, high-performance web applications for a wide spectrum of industries like e-commerce, logistics, fintech, and many more.
Our development team is highly skilled at optimizing data fetching, caching, and background synchronization to create seamless, responsive web applications that keep users engaged without compromising performance.
Whether you're developing a complex enterprise solution or a customer-facing app, we focus on crafting high-performance, reliable applications personalized to your needs. Discover how we can help you build a fast, scalable, engaging product that delivers results.
React JS Developer
Manish Giri Goswami is a passionate Frontend Engineer with expertise in React JS, Next JS, JavaScript, and TypeScript. With a strong foundation in web development, he continuously explores new technologies to enhance his skills. A dedicated Computer Science enthusiast, he thrives on solving challenges and building innovative web applications.
React Query is a powerful data-fetching and state management library for React applications. It abstracts away the complexities of working with async data, offering features like caching, synchronization, pagination, and background data refetching. By using React Query, you can easily manage server-side state in a declarative manner.