48

I'm trying to invalidate queries every times users press button "likes" to refresh all queries but without success so far despite following the docs.

I have a component that get data :

  const {
    data: resultsEnCours,
    isLoading,
    isError,
  } = useQueryGetEvents("homeencours", { currentEvents: true });

This is a custom hook which look like this :

const useQueryGetEvents = (nameQuery, params, callback) => {
  const [refetch, setRefetch] = React.useState(null);
  const refetchData = () => setRefetch(Date.now()); // => manual refresh
  const { user } = React.useContext(AuthContext);
  const { location } = React.useContext(SearchContext);

  const { isLoading, isError, data } = useQuery(
    [nameQuery, refetch],
    () => getFromApi(user.token, "getEvents", { id_user: user.infoUser.id, ...params }),
    // home params => "started", "upcoming", "participants"
    {
      select: React.useCallback(({ data }) => filterAndSortByResults(data, location, callback), []),
    }
  );

  return { data, isLoading, isError, refetchData };
};

export default useQueryGetEvents;

And I have another component "ButtonLikeEvent" which allow the user to like or unlike an event:

import { useMutation, useQueryClient } from "react-query";
import { postFromApi } from "../../api/routes"; 
...other imports


const ButtonLikeEvent = ({ item, color = "#fb3958" }) => {
  const queryClient = useQueryClient();
  const {
    user: {
      token,
      infoUser: { id },
    },
  } = React.useContext(AuthContext);

  const [isFavorite, setIsFavorite] = React.useState(item.isFavorite);
  const likeEventMutation = useMutation((object) => postFromApi(token, "postLikeEvent", object));
  const dislikeEventMutation = useMutation((object) =>
    postFromApi(token, "postDislikeEvent", object)
  );

  const callApi = () => {
    if (!isFavorite) {
      likeEventMutation.mutate(
        { id_events: item.id, id_user: id },
        {
          onSuccess() {
            queryClient.invalidateQueries();
            console.log("liked");
          },
        }
      );
    } else {
      dislikeEventMutation.mutate(
        { id_events: item.id, id_user: id },
        {
          onSuccess() {
            queryClient.invalidateQueries();
            console.log("disliked");
          },
        }
      );
    }
  };

  return (
    <Ionicons
      onPress={() => {
        setIsFavorite((prev) => !prev);
        callApi();
      }}
      name={isFavorite ? "heart" : "heart-outline"}
      size={30}
      color={color} //
    />
  );
};

export default ButtonLikeEvent;

Every time an user click on that button I'd like to invalidate queries (because many screens shows the like button).

The console.log is displayed on success but the queries are not invalidate.

Any idea ?

thanks

4
  • I have "similar" issue but React Query Devtools is not helpful and even masking the problem, making it worse. With Devtools enabled, the invalidation works well. However without it, the invalidation doesn't work. So it becomes a Schrodinger's cat for me. React Query and React Query Devtools 5.12.2. Commented Dec 2, 2023 at 11:16
  • I tried to downgrade to React Query v4.x but still cannot make it work. Both with v4.x and v5.x React Query Devtools doesn't help me. And when a MUI Drawer opens, React Query Devtools suddenly clears all shown caches. Commented Dec 2, 2023 at 11:26
  • 1
    awaiting queryClient.invalidateQueries() helped me Commented Dec 6, 2023 at 8:49
  • I don't understand why react query is so popular after seeing so many people struggle with such a fundamental operation Commented Feb 4 at 14:29

23 Answers 23

68

Also, there is one more reason that was not mentioned before:

If you used enabled option in useQuery than such queries are ignored by invalidateQueries. From docs:

https://tanstack.com/query/latest/docs/react/guides/disabling-queries

The query will ignore query client invalidateQueries and refetchQueries calls that would normally result in the query refetching.

Sign up to request clarification or add additional context in comments.

5 Comments

This is exactly the answer I needed. Thank you so much!
This seems to be the reason for my troubles as well! How do I invalidate this hook then if invalidateQueries is ignored?
@bombillazo I don't know the suggested way, but I personally changed from useQuery to useMutation that makes a GET request.
Up to date link for react-query disabling-queries: tanstack.com/query/latest/docs/react/guides/disabling-queries
My god this is such a dumb, obscure gotcha. Fixed my issue as well, and I've been scouring docs forever.
62

I accidently used

const queryClient = new QueryClient();

instead of

const queryClient = useQueryClient();

new QueryClient() creates a new react-query Client(which is usually passed to a context wrapped over your App), this should be done only once.

wheres useQueryClient returns a reference to the value that the react-query Client context wrapped around your app provides. You should use it inside your components.

2 Comments

can you even work with tanstack query properly when not doing this? :o
I had the same issue with Tanstack Query v5 and it worked like a charm. export const useContactDelete = () => { const queryClient = new QueryClient(); return useMutation({ mutationFn: (contactId) => deleteContact(contactId), onSuccess: async (res) => { // setQueryData await queryClient.invalidateQueries({ queryKey: ["contacts", "list"], }); await queryClient.invalidateQueries({ queryKey: ["contacts", "info", res.id], }); }, }); };
27

The two most common issues why query invalidation is not working are:

  • keys are not matching, so you are trying to invalidate something that doesn't exist in the cache. Inspecting the devtools helps here. Oftentimes, it's an issue with number/string (for example, if an id comes from the url, where it is a string, but in your key, it's a number). You can also try manually clicking the invalidate button in the devtools, so you will see that invalidation per-se works.

  • the queryClient is not stable across re-renders. This happens if you call new QueryClient() inside the render method of a component that then re-renders. Make sure that the queryClient is stable.

2 Comments

Thank you for your insight. Unfortunately I can't use devtools as i'm using react-native. My queryClient is stable also as I'm declaring it in app.js.
The stale client was my case. Thank you Explanation and solution: tkdodo.eu/blog/…
11

In my case, all I had to do was await the promise that invalidateQueries returns before navigating away (using react-router).

Comments

10

In my case, invalidate queries didn't work because I tried to invalidate query that was used in another page (the useQuery that used this case wasn't rendered).

I had 2 options to solve this:

  • use refetchInactive option like this: queryClient.invalidateQueries(key, { refetchInactive: true }), which will refetch the data instead of "stale" it.
  • use refetchOnMount: true option when calling useQuery, which will refetch the data on mount regardless if it is stale.

2 Comments

Many thanks to this answer for the pointer to "inactive queries"! I was using TanStack Query + Router and trying out loaders in Router that would provide data for routes like loader: () => await queryClient.ensureQueryData(/* query key and callback here */). But by default invalidateQueries() only forces refetch for active queries, and for some reason the loader doesn't count as being "active", so I had to add the prop refetchType: 'all' to invalidateQueries().
Thank you. I have a similar case and I did queryClient.invalidateQueries({ queryKey: ['key'], refetchType: 'all' } using v5.18.0
9

UPDATE: My issue was I incorrectly set up React Query as follows:

export default function App() {
  const queryClient = new QueryClient();
  return (
    <QueryClientProvider client={queryClient}>
      ...
    </QueryClientProvider>
  );
}

Fix:

const queryClient = new QueryClient();

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      ...
    </QueryClientProvider>
  );
}

Subtle, but changes everything.


My issue is that I have react-router-dom's useNavigate() .navigate(), i.e.:

const router = useRouter();
router.push(`${path}?field=${name}`);

which either clears the cache or remounts components, but the important effect is the cache is cleared (can be observed through React Query Devtools).

Since the cache is cleared but the components stay, doing invalidateQueries() has no effect, as there's nothing on the cache.

It is the exact issue described here: https://www.reddit.com/r/learnreactjs/comments/uqd9sl/reactquery_cache_being_cleared_when_navigating/

I don't know how to solve this, so my "workaround" is to avoid using navigate() when possible. Although this is not ideal and I think I will get this issue again later. :'(

  • react-query v5.12.2
  • react-router-dom v6.16.0

2 Comments

This! is the correct answer :)
This saved me brother, thank you
8

Both Samil and Eliav ideas combined worked for me:

  1. QueryClient is only for initializing the QueryClientProvider.
const queryClient = new QueryClient();
  1. useQueryClient is used everywhere else.
const queryClient = useQueryClient();

1 Comment

this is a really important distinction. useQueryClient will use the root queryClient that wraps your react application and has all the query keys
2

QueryClient should be instantiated on the top of your app. (app.js or index.js)

Sample

import { QueryClient, QueryClientProvider } from 'react-query';

const queryClient = new QueryClient();
ReactDOM.render(
    <QueryClientProvider client={queryClient}>
    <App />
    </QueryClientProvider>,
document.getElementById('root'));

Comments

2

In my case I was trying to use the queryClient in a component with a higher level than the QueryClientProvider in the components tree. You have to use the useQueryClient hook in a component wrapped by the QueryClientProvider.

<MyFirstComponent> <!-- I was using it in My FirstComponent. It is outside the QueryClient Context so it is not going to work there -->
  <QueryClientProvider>
    <MySecondComponent /> <!-- You can use the queryClient here -->
  </QueryClientProvider>
</MyFirstComponent>

Comments

2

I had the same issue and in my case the problem was that I imported the wrong client.

I used vscode's autocomplete and accidentally imported it from wagmi instead of @tanstack/react-query.

enter image description here

1 Comment

Oh Wow, thank you. I was about to re-implement all the queries using my own hooks. This issue is pretty annoying, it also happened once before with Mui theming, I was importing the wrong createTheme() function.
2

For me nothing worked, so i just detect a route change in the parent component and refetch when the route changes:

import { useLocation } from 'react-router-dom';

...

const location = useLocation();
const { data: tagNames, refetch } = useGetTagNames();

...

useEffect(() => {
  refetch();
}, [location]);

Comments

2

In my case I supplied a number type as a query key in useQuery and string in invalidateQueries, thus not invalidating the query.

Comments

2

When using ensureQueryData to retrieve data from cache, one interesting challenge surfaced: the invalidateQueries API method did not work for data invalidation 😱😱😱. I wanted to retrieve data from cache if exist or fetching otherwise.

Thankfully, React Query has a different API for this particular situation:

queryClient.removeQueries({ queryKey: ["queryKey"] });

1 Comment

might be not the most subtle touch for my case, but did the trick! :)
1

An additional reason for why your queries might not be properly invalidating is if you aren't returning the promise-based function in mutationFn.

In my case, I made the mistake of calling the promise-based function without returning it. For example:

function useUpdateObject(id) {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (newObj) => {
      // MISTAKE! Need to return ky.put(...)
      ky.put(API_ROUTE, { json: newObj }).json()
    },
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ["object", id],
      });
    },
  });
}

Comments

1

In my case the invalidateQueries did not work after the app was hot-reloaded (after a change).

Very weird behaviour, but it takes a while before you figured out the hot-reloading is the culprit.

Comments

1

My problem was that i had two QueryClientProvider one in layout ans second in other layout

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
0

I had the same problem where everything was working correctly locally, but not in production. The issue was due to the cache of my Nginx server preventing data updates. To fix this, I had to add the directive

add_header 'Cache-Control' 'no-cache' always;

to my Nginx server configuration.

Comments

0

My issue was that I had:

const { isLoading, data } = useQuery(
  CACHE_KEY,
  async () => apiFunction(foo)
);

When in fact I needed:

const { isLoading, data } = useQuery(
  CACHE_KEY,
  async () => await apiFunction(foo)
);

Very subtle and easy to miss!

Comments

0

It seems like I have been dropped on my head in my childhood because I made a custom hook and spread the config props in the wrong way.

Do this:

const { onSuccess, ...restConfig } = config || {};

return useMutation({
  onSuccess: (data, variables, context) => {
    queryClient.invalidateQueries({
      queryKey: [productsQueryKeys.PRODUCTS],
      exact: true,
    });

    onSuccess && onSuccess(data, variables, context);
  },
  ...restConfig, <---- make sure you exclude the fields you're using
  mutationFn: createProduct,
});

Instead of this:

const { onSuccess } = config || {};

return useMutation({
  onSuccess: (data, variables, context) => {
    queryClient.invalidateQueries({
      queryKey: [productsQueryKeys.PRODUCTS],
      exact: true,
    });

    onSuccess && onSuccess(data, variables, context);
  },
  ...config, <---- who raised you like this?
  mutationFn: createProduct,
});

Comments

0

WHEN USING SSR IMPLEMENTATION WITH NEXTJS 13

When following the implementation enforced on TanStack's docs, you would be encouraged to create a request based client for Server Side and a singleton client for Client Side.

But when using queryClient.invalidateQueries, be sure to use queryClient from useQueryClient instead of importing your global client.

Here is an example of how to do it when invalidating in a mutation.

export const useAddProductToCartMutation = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (data: { productId: number }) => {
      return await addProductToCart(data.productId);
    },
    onSettled: async () => {
      queryClient.invalidateQueries({
        queryKey: ["cart"],
        refetchType: "all",
      });
    },
  });
};


Comments

0

As suggested by GamsBasallo and N.T. Dang on MasterPiece's answer, if you're using ReactRouter, on the action function you have to add the refetchType to all and await the invalidating function. That is because queries are in inactive state inside ReactRouter action function.

Helpful doc :

https://tanstack.com/query/latest/docs/reference/QueryClient/#queryclientinvalidatequeries

https://tkdodo.eu/blog/react-query-meets-react-router#invalidating-in-actions

Comments

0

In my case, I was trying to invalidate two queries at the same time:

queryClient.invalidateQueries({ queryKey: ['profile', 'userTopics'] })

When I separated it, then it worked

queryClient.invalidateQueries({ queryKey: ['profile'] })
queryClient.invalidateQueries({ queryKey: ['userTopics'] })

Comments

-1

Same issue. Success and error at the same time. The problem was in that line:

const queryClient = useQueryClient

I've just forget brackets in the end.

const queryClient = useQueryClient()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.