Enhancing Performance with React Query's Caching
Adding caching helps get better performance, and this article shows how the React Query library can help with this by properly using the cacheTime and staleTime parameters.
Discover how at OpenReplay.com.
Consuming APIs and interacting with databases are fundamental aspects of developing web applications. The React library does not have a concise holistic approach to fetching data from a server; therefore, React developers came up with a way to work with external servers using the React effect
hook.
Although this method works well to the best of its capabilities, you would usually find yourself writing a lot of code that will handle a particular request. This method would require you to work with two or more states to deliver an almost seamless application.
But what if I told you that there is a library that would make the data fetching needs of your application chaos-free and, of course, with fewer lines of code?
React query, like the documentation says, “a missing data-fetching library for web applications, it makes fetching, caching, synchronizing, and updating server state in your web applications a breeze.” Now, take note of “caching”; it is one of the key aspects we will discuss in this article.
In its simplest terms, caching means to store frequently used data in an easily accessible place temporarily. Imagine a scenario where you have your favorite comfortable clothes, and you wear them frequently. Logically, you would want to keep them somewhere you can easily pick them up to wear rather than storing them in a box where you keep all other clothes you do not wear frequently. Now, what does this do for you? Whenever you need to step out of your house, it takes you less time to get dressed in your go-to clothes than if they were stored in a box deep in your closet, making you punctual to wherever you want to go.
This is exactly what caching does in web browsers, too; it stores frequently used items or data in the cache so that the next time you visit that website or while you’re using that website, the browser can quickly retrieve this data from the cache rather than fetching it from the database on every single refresh. This ultimately makes the user experience more enjoyable while surfing through the internet.
The React query library provides us with effective cache management where we can customize how and when we want our data to be cached while using an application.
Challenges Posed by Data Queries without Effective Cache Management
Bandwidth is the maximum amount of data transferred over a network connection. Whenever we need to fetch data from a server, it is done over a network, and this data transfer from the server to us consumes bandwidth.
If a bandwidth that is designed to receive 50Mbps(megabytes per second) receives 100Mbps, this would mean that the bandwidth is being stretched from its original size. This could lead to slower internet speed and slower application performance, the two things that users detest.
Imagine that we have ten users on an e-commerce application; when they log in, the website must retrieve data from the server to populate its contents. This happens every single time for the ten users and occasionally five additional users. Chances are, it would take more time to populate the website with content from the server for the fifteen people than if it was just one person who needed that data then.
This situation can be likened to a highway designed for only 100 cars to pass at a time, but on a particular day, about 200 cars are on that highway; what do you think would happen? This would result in traffic congestion on the highway, delaying the drivers of these cars from getting to their destination.
The Browser Cache
All web browsers possess a cache that is external to every application. The browser cache is tasked with temporarily storing static files; these files include HTML files, CSS Stylesheets, images, videos, JavaScript files, etc., depending on configurations from the server. When a user visits a website for the first time, the browser downloads these static files from the server and stores them in the browser cache. This process also contributes to decreasing the load times of our applications and ultimately improves user experience.
React Query comes with its unique cache management strategy designed to manage asynchronous data and server state applications. Unlike the browser cache that manages static files, the React Query cache is designated to handle dynamic data, such as data from an API or a database, which could change at any time.
You need not worry about any conflict between the browser cache and React Query cache management because the browser cache specifically handles the structure of the website. In contrast, the query cache handles dynamic content on the website during the runtime of your application.
Now, with React Query’s cacheTime
option, we can further improve our users’ application experience.
What is cacheTime?
Earlier in the article, we stated the meaning of cache and what it does in web applications. cacheTime
defines how long a certain piece of data will be stored in the cache before it expires and is re-fetched from the server. With React Query, we can manipulate the cacheTime
to meet the needs of our applications for a better user experience.
cacheTime
in React query by default is five minutes only; this means that when you fetch data for the first time from the server, it will be stored temporarily in the cache for five minutes, so within that period, if the user reloads that page the user will not have to see a loading indicator every single time, what the user will see instead is just a cached version of the data that is already made available to him immediately and he doesn’t have to wait for a transfer from the server.
Now, let us see how this works in practice. I will run a JSON server to mock a data fetch from a server using the React query library.
From the clip above, you can see that the first time I navigated into the superheroes page, there was a very brief loading state before the data was displayed on the screen; you can also see the cache time
in the React query developer tools at 300000ms which is equivalent to five minutes.
Also, notice from the clip above that I no longer see the loading state when I leave the superheroes page and navigate back the second time. This means that the loading state is set to true when we are fetching data directly from the server the very first time. Still, subsequent times within the five-minute cacheTime
data is gotten from the cache cutting down unnecessary server requests, thereby reducing bandwidth usage and increasing network speed.
const useFetch = (queryKey, url) => {
const {
isInitialLoading,
data,
isError,
error,
isFetching,
} = useQuery([queryKey, url], () => fetchData(url), {
enabled: false,
onSuccess,
onError,
cacheTime: 0,
});
return { isInitialLoading, data, isError, error, isFetching };
};
You can manually change the cacheTime
like I just did in your codebase. I have set cacheTime
to zero, which means that no data will be stored in the cache, and each time the user requests, the data will always have to come from the server regardless.
In the same way, you can set your cacheTime
to zero; you can also increase your cacheTime
to be longer than five minutes, depending on the needs of your application.
Do not get confused if you notice on your network tab that there is some fetching process going on. Does this mean that our data was not cached?
No, that is not the case here,
This is the workflow:
- The user logs into the website for the first time.
- Data is retrieved from the server and displayed to the user.
- Retrieved data is stored in the cache for five minutes.
- Background fetch goes on in the background, checking for any updates in the database
- For five minutes, the user only sees data from the cache. Still, if there is any update in the database, the background fetch ensures that the cached data is updated with the new input from the database, thereby keeping the user up to date.
Do not be confused with this background fetch; it only updates the cache data. It does not mean the same data comes from the server on every page load.
This brings us to a new concept called staleTime
.
What is staleTime?
Like the name implies, “stale” is often associated with things that are no longer fresh or original. For example, when someone says “tomatoes are stale,” it means they’re no longer fresh and have probably been in the kitchen for a few days or weeks.
staleTime
in this context is how long your data will remain fresh before it turns stale. By default, staleTime
for React query is zero seconds; this means that immediately you fetch data from a server, it becomes stale immediately.
Just like cacheTime
can be manipulated, staleTime
can also be changed by specifying it as part of your options when making a query, as shown below:
const useFetch = (queryKey, url) => {
const {
isInitialLoading,
isLoading,
data,
isError,
error,
isFetching,
} = useQuery([queryKey, url], () => fetchData(url), {
enabled: false,
onSuccess,
onError,
staleTime: 300000,
});
return { isInitialLoading, data, isError, error, isFetching };
};
The code above ensures that staleTime
for our data is five minutes. Therefore, our data will remain fresh until five minutes before it turns stale.
You might be wondering, what is the point of keeping your data fresh?
The React developer tools in the image above shows us that our data is still fresh by making the fresh tab visible. When your data remains fresh, you no longer make any network requests to the server via the background fetch we discussed earlier because the data is still fresh. The data that will be shown to the user at this point is fresh data stored in the cache without any more network requests from the server until the data is no longer fresh.
In the clip below, we navigate away from that particular route where data is being fetched and returned to the route; you will notice in the network tab that no request is being made to the server because our data is still fresh. This further reduces strain on the bandwidth, increasing application performance and user experience.
These are the key differences between cacheTime
and staleTime
;
cacheTime | staleTime |
---|---|
The default time is five minutes. | The default time is zero. |
Users can see data stored in the cache but with a silent background fetch to update data in the cache. | Users can see data stored in the cache but without background fetch. |
Ideal Situations for Adjusting cacheTime and staleTime for Performance
It might be a little confusing to decide when you should adjust your cacheTime
and staleTime
, but the two examples below could guide you to decipher cases where you would need to make adjustments in your projects.
-
If the data to be shown to the user is the sort that does not change frequently, this is a good opportunity to increase the
staleTime
of the query. The first example could be a cinema website, where users check out available movies for the week and book a ticket. You already know that the movies for the week will not change until a new week, so there is no point letting the browser run any background refetch. Therefore, the data does not need to go stale immediately, and you can still keep the data for the movies of the week fresh during the application’s run time before it goes stale. -
The product listings for an e-commerce website do not change rapidly, so you do not need to constantly call the backend server to populate the website with product information. You can simply adjust the
cacheTime
andstaleTime
; this would reduce strain on the bandwidth and ensure that users experience faster load times.
Recognizing cases for default cacheTime and staleTime
You do not always have to change the values of your cacheTime
and staleTime
; depending on the project you are working on, you may want to maintain their default values.
- The most common scenario is for applications where data changes frequently.
- Examples include stock prices, current exchange rates, cryptocurrency data, and social media feeds (e.g., Twitter).
- Default
cacheTime
andstaleTime
are suitable to ensure users receive fresh data regularly.
The goal is to ensure that users stay up-to-date, receiving fresh data as frequently as possible for certain applications.
Why staleTime should be lower than cacheTime
It’s essential to note that staleTime
should always be lower than cacheTime
. Picture this: you set a cacheTime
of five minutes and a staleTime
of 10 minutes. In this scenario, you anticipate having fresh data for ten minutes.
However, your cacheTime
runs out after five minutes, and the data that should still be fresh is prematurely emptied from the cache. Consequently, you lose data freshness with five minutes remaining, leading to another request to the server.
Ensure not to lose fresh data before its time elapses; set your cacheTime
higher than the staleTime
; this will keep your fresh data intact until its time elapses before it gets emptied from the cache.
In the code below, the cacheTime
is set higher than the staleTime,
ensuring we do not lose fresh data from the cache before its time elapses.
const useFetch = (queryKey, url) => {
const {
isInitialLoading,
isLoading,
data,
isError,
error,
isFetching,
} = useQuery([queryKey, url], () => fetchData(url), {
staleTime: 300000, //5mins
cacheTime: 600000, //10mins
});
return { isInitialLoading, data, isError, error, isFetching };
};
Cache Invalidation and Revalidation
Recall earlier in this article that when your data has become stale, the React query performs a background fetch even with data in the cache to ensure that the user is kept up to date with current data that may be added to the server. Automatically, React Query revalidates the cached data when there is an update to the database or API.
But in a situation where you have set the staleTime
and the data is still fresh, as explained above, there is no background fetch going on, and if there was an update to the database, the user would not be aware. React Query provides the refetch
function, which can fetch data directly from the server regardless of the current staleTime
.
const useFetch = (queryKey, url) => {
const { isInitialLoading, data, isError, error, refetch, isFetching } =
useQuery([queryKey, url], () => fetchData(url), {
staleTime: 300000,
cacheTime: 600000,
});
return { isInitialLoading, data, isError, error, refetch, isFetching };
};
const SuperHeroesPage = () => {
const {
isInitialLoading,
data,
isError,
error,
refetch,
isFetching,
isLoading,
} = useFetch("supaheroes", "superheroes");
if (isInitialLoading || isLoading) {
return <h1>loading....</h1>;
}
if (isError) {
return <h1>{error.message}</h1>;
}
return (
<>
<div className="button-container">
<button onClick={() => refetch()} className="button">
Retrieve Data
</button>
</div>
{data &&
data.data?.map((superhero) => (
<div key={superhero.alterEgo} className="superheros">
<h3>Name: {superhero.name}</h3>
<p>Alter Ego: {superhero.alterEgo}</p>
</div>
))}
</>
);
};
export default SuperHeroesPage;
In the code block above, I have attached the refetch function provided by React query to a button. Upon clicking the button, a request for fresh data from the server is automatically triggered. This action ensures that users receive up-to-date information, regardless of the specified staleTime
or cacheTime
.
Conclusion
Suppose you have read this article with me thus far. In that case, you can see how React Query’s staleTime
and cacheTime
could significantly improve user experience and application performance. React Query not only takes away the hassle of unnecessary states and writing longer lines of code but also comes with a user-first approach.
It gives developers the freedom to customize their application’s data query needs and improve load times for application users by tweaking the cacheTime
and the staleTime
. This is the official documentation for more of the amazing features and options with React Query.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.