Tanstack Query is great, it has many excellent features - one of which is the caching of query results. But what if I need to manipulate the cache? What are the options, and when should I use them? And what does it even mean that my data is stale?
What is Tanstack Query?
Tanstack Query (a.k.a React Query) is a popular library for managing asynchronous data (-> query). Its most common use is managing data fetching logic in React and other applications - it provides an easy API and automatic cache; it also tries to fetch the data if something goes wrong, and its default configuration is sufficient for most applications. It is also great to manage mutations - asynchronous changes, which again provides great API and allows us to declare all the mutation's side effects. This is also complemented by its DeveloperTools where a developer can see all the queries in the application and even change their state for easy debugging.
Example of both query and mutation can look like this:
const {data, isPending ,error} = useQuery({
queryKey: ["users"],
queryFn: async () => {
const resp = await fetch("https://example.com/api/users");
// TODO check response
return resp.json();
}
});
const {mutate: addUser} = useMutation({
mutationFn: async (newUser) => {
const resp = await fetch("https://example.com/api/users", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(newUser)
});
// TODO check response
return resp.json()
},
onSuccess: (data) => {
toast("User created", "success");
redirect("/users/" + data.id);
},
onError: (e) => {
toast("Something went wrong", "error");
logger.error(e);
}
})
Manipulating the cache in Tanstack Query
Let's imagine a fairly common situation: We have fetched a list of users from our server displayed in our app. Since we are using Tanstack Query, all the data is cached and is therefore immediately available when we, for example, leave the page and then return. Then we add a new user through a request to the server; what about the data in our cache?
Tanstack Query provides an object called "QueryClient", which has several methods for managing its cache, each suitable for different situations. Most of them allow us to apply them to all queries or choose to apply them to only some.
Invalidation (QueryClient.invalidateQueries)
Invalidation, the most commonly used cache manipulation technique, ensures a predictable process. It marks queries' data as stale and triggers the prefetching of active queries. Until new data is available, each query displays the old data. This predictability is further enhanced by the promise it returns, which resolves when the prefetching process finishes.
💡 Active queries are those currently being used in a rendered component.
This behavior is ideal when it's okay for users not to see the new data immediately or when there is no need for a loading indicator. It can also be used directly in the mutation, which slightly prolongs the mutation, but after that, users can already see the latest data.
Removal (QueryClient.removeQueries)
Removal makes the QueryClient “forget” about its queries. Inactive queries lose their data and, after rendering, must fetch it; active ones still “remember” their data until the next rerender.
To me, this is suitable for actions where the user shouldn't be able to see any data - that might mean signing out sometimes this might also make sense when switching the app's language.
Resetting (QueryClient.resetQueries)
This method is similar to removal but only removes queries' data. It still "remembers" all the queries; the active ones are then notified that the data no longer exists, and they should re-fetch it.
This might be useful when the mutation significantly affects the whole page, e.g., when the user upgrades their account to a level that allows new functionality or otherwise changes page elements.
Setting data (QueryClient.setQueryData)
Setting data manually can be used when it's easy to figure out the state without needing to refetch it again. This might be useful when the state is returned from the server in the initial mutation, the new state is primitive (e.g., hiding a banner that is shown based on a boolean query), or some other optimistic mutation.
⚠️ It might be beneficial to combine it with invalidation - the user will instantly see the expected data and then the new data as soon as we obtain it. Furthermore this might help us spot any potential bugs in our expected data, since the difference should be visible right after fetching the real one.
Other methods
Stale queries
Queries with stale data are automatically refreshed when they are remounted or after refocusing the window. Queries are marked as stale automatically after a certain period (0 by default), which can be configured.
Summary: Options for manipulating cache in Tanstack Query
This article has shown how to manipulate cache in Tanstack Query, the differences between approaches, and when each could be used. If you prefer to try it yourself, there is also a deployed demo project.
ℹ️It is essential to note that most methods allow vast customization, and the described behavior applies to the default configuration. How do You work with cache? Do you use the same methods as us, or do you even use a library different from Tanstack Query? Hit us up on Twitter.
Sources: Tanstack Query Demo project Demo project