Fine-tuning invalidateQueries with React Query (v4)

Written by Codemzy on October 13th, 2022

This post will cover how you can specify which queries to invalidate in ReactQuery with queryClient.invalidateQueries(). Here's how to target single queries, multiple queries, active queries and inactive queries.

I've just started using React Query on one of my projects and it makes fetching and caching data from the server so much easier. I'm super impressed!

And if you extend staleTime, you can reduce API requests - which is great if you're just refetching the same data. I also love how React Query can handle paged data with the keepPreviousData option to reduce the need for loading spinners and create smoother transitions.

By setting keepPreviousData to true we get a few new things:

  • The data from the last successful fetch available while new data is being requested, even though the query key has changed.
- TanStack Query Paginated / Lagged Queries

But if you do extend staleTime (and maybe cacheTime), what happens if you need to get fresh data sooner?

For example, if the server lets you know when you fetch page 7 that new data has been added to the collection. Or your user has added a new item.

That's when invalidateQueries() comes to the rescue!

What does invalidateQueries() do?

If you have data stored in React Query that's considered fresh, you can keep reusing it in your application, and save repeated API requests to your server.

That's great until it isn't!

Maybe something has changed and the data just isn't right anymore, so you need to sync back up with the server quickly. How do you get React Query to throw that data away? You turn it stale!

invalidateQueries() does a couple of things. The first one is fairly obvious - as the name suggests, it invalidates queries. So any cached data that is considered fresh, is turned stale.

And secondly, it triggers a refetch for all of the data being used. This post will also cover how you can tweak the settings for this.

How to use invalidateQueries()

You can find invalidateQueries() on the queryClient. Here's an example of invalidating EVERY query in the cache.

import { useQueryClient } from '@tanstack/react-query';
// get the query client
const queryClient = useQueryClient();

// invalidate all queries in the cache

This won't refetch every query - just the active ones! Here's how you can force refetch with React Query.

But chances are, you don't need to invalidate all of your queries, you probably just want to invalidate the ones related to whatever data has changed.

So let's look at how you can fine-tune invalidateQueries.

Using invalidateQueries on a single query

If you're using react query, you probably already know about query keys.

A query is a declarative dependency on an asynchronous source of data that is tied to a unique key.

TanStack Query - Queries > Query Basics

Because each query has a unique key, you can use this to invalidate a specific query.

For example, let's say you have a list of staff. It doesn't change very often, so you set a staleTime of 15 minutes.

import { useQuery } from '@tanstack/react-query';

// fetch staff data
const { data } = useQuery(['staff'], fetchMyStaff, { 
  staleTime: 15 * (60 * 1000), // 15 mins 

But 5 minutes later, you added a new staff member, so need to invalidate the staff query to reload the latest data.


This would invalidate the query 'staff'. But what about if you stored data on each member of staff too? And those queries started with 'staff' e.g.

  • ['staff', 'details', { name: "Joe B" }]
  • ['staff', 'details', { name: "Mary F" }]
  • ['staff', 'details', { name: "Sam W" }]

Those queries would also be invalidated - because they start with 'staff', and the power of React Query is that you can match multiple queries with their prefix.

But if you've only added a new staff member, the details for each existing staff member are still valid. To get around this, you can add the { exact: true } option.

queryClient.invalidateQueries(['staff'], { exact: true });

Now only the single query ['staff'] is invalidated.

Using invalidateQueries on multiple queries

We've kind of already looked at multiple queries when queryClient.invalidateQueries(['staff']) invalidated all the queries starting with 'staff'. But you can get pretty inventive with it, so let's look at some more examples.

Maybe you don't want to invalidate the staff list, because all the staff are the same, but their details have changed.

queryClient.invalidateQueries(['staff', 'details']);

Sticking with the staff example, let's imagine you had roles for each of the staff members.

  • `['staff', 'details', { name: "Joe B", role: "Manager" }]
  • `['staff', 'details', { name: "Mary F", role: "Supervisor" }]
  • `['staff', 'details', { name: "Sam W", role: "Assistant" }]

And you wanted to invalidate all queries for staff with the supervisor role.

queryClient.invalidateQueries(['staff', 'details', { role: "Supervisor" }]);

Or what about something even trickier? You want to invalidate anyone who is not a Manager. Hmmm, that could be hard, especially if you don't know all of the other roles. And even if you did, you wouldn't want to list them all out in your code.

queryClient.invalidateQueries(['staff', 'details', { role: "Supervisor" }]);
queryClient.invalidateQueries(['staff', 'details', { role: "Assistant" }]);
queryClient.invalidateQueries(['staff', 'details', { role: "Administrator" }]);
queryClient.invalidateQueries(['staff', 'details', { role: "Receptionist" }]);

What if other roles were added in the future?

Invalidating with the predicate function

For really tricky invalidations, you can pass a predicate function to React Query, so you can do a comparison on the query key.

Here's how you can check if the role is not a manager.

  predicate: function(query) {
    return query.queryKey[0] === 'staff' && 
    query.queryKey[1] === 'details' && 
    query.queryKey[2]?.role !== "Manager";

Pretty amazing!

Invalidating active or inactive queries (or both)

Often, I've found that I need to invalidate queries due to a response from the server. Let's say you're fetching page 7 of some data.

import { useQuery } from '@tanstack/react-query';

// fetch data
const { data } = useQuery(['my-list', 7], fetchMyList);

And for some reason, the response indicates the rest of your data (pages 1-6) are no longer valid. Maybe items have been added or removed from the list, for example.

Now, you've just fetched page 7. So that data is valid. Only your other pages are invalid. Instead of running some complicated predicate function to check if the page is not 7, you can just invalidate inactive queries, since page 7 is the query that's active.

queryClient.invalidateQueries(['my-list'], {
  type: 'inactive', // only invalidate inactive queries

type defaults to all, but you can set it as active or inactive to fine-tune your query selection.

Deciding when to refetch invalid queries

And finally, when data is stale - because you invalidated it - it gets refetched! By default, any queries you have invalidated that are currently active will get refetched. The others will wait as state data until they are needed and get refetched then.

But you can fine-tune this too, with refetchType.

If you don't want any of your queries to refetch right away, and would rather wait until they are next requested (with a useQuery), you can set refetchType to none.

queryClient.invalidateQueries(['my-list'], {
  type: 'inactive', // only invalidate inactive queries
  refetchType: 'none', // don't refetch until needed

If instead, you want to refetch all of the other pages, you can set refetchType to inactive or even all, since you've only marked the inactive pages as stale.

queryClient.invalidateQueries(['my-list'], {
  type: 'inactive', // only invalidate inactive queries
  refetchType: 'inactive', // refetch all inactive stale data

refetchType defaults to active, but you can set it as inactive, all or none.

I hope this post helps you banish old data and keep your queries fresh!