Invalidate queries doesn't work [React-Query]
Asked Answered
C

22

36

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

Curassow answered 29/7, 2021 at 14:40 Comment(3)
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.Yvoneyvonne
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.Yvoneyvonne
awaiting queryClient.invalidateQueries() helped mePolypoid
G
52

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.

Guaiacol answered 13/5, 2022 at 13:33 Comment(4)
This is exactly the answer I needed. Thank you so much!Antihistamine
This seems to be the reason for my troubles as well! How do I invalidate this hook then if invalidateQueries is ignored?Ramon
@Ramon I don't know the suggested way, but I personally changed from useQuery to useMutation that makes a GET request.Guaiacol
Up to date link for react-query disabling-queries: tanstack.com/query/latest/docs/react/guides/disabling-queriesShell
S
49

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.

Submerged answered 11/3, 2022 at 20:40 Comment(3)
My exact problem. Thank youLeaper
can you even work with tanstack query properly when not doing this? :oPolypoid
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], }); }, }); };Arsenical
S
20

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.

Spile answered 30/7, 2021 at 9:17 Comment(2)
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.Curassow
The stale client was my case. Thank you Explanation and solution: tkdodo.eu/blog/…Coachwork
C
10

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

Cassel answered 31/5, 2022 at 15:18 Comment(1)
Me too. Wasn't aware it was a promise. Thanks!Cashbox
D
8

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.
Din answered 14/6, 2023 at 8:21 Comment(2)
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().Amaryllis
Thank you. I have a similar case and I did queryClient.invalidateQueries({ queryKey: ['key'], refetchType: 'all' } using v5.18.0Mcquillin
M
5

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();
Manciple answered 26/4, 2022 at 18:30 Comment(0)
Y
3

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
Yvoneyvonne answered 2/12, 2023 at 12:52 Comment(1)
This! is the correct answer :)Belloir
P
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'));
Preston answered 14/2, 2022 at 13:37 Comment(0)
G
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

Guyguyana answered 29/5, 2023 at 11:28 Comment(1)
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.Dunning
H
1

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>
Histrionic answered 25/1, 2023 at 21:0 Comment(0)
C
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.

Comptometer answered 14/4, 2023 at 9:15 Comment(0)
B
1

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

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

queryClient.removeQueries({ queryKey: ["queryKey"] });
Breslau answered 26/11, 2023 at 23:32 Comment(1)
might be not the most subtle touch for my case, but did the trick! :)Polypoid
G
1

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]);
Gorlovka answered 8/5 at 5:59 Comment(0)
Y
0

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],
      });
    },
  });
}
Yettie answered 28/3, 2023 at 21:0 Comment(0)
R
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.

Riboflavin answered 30/4, 2023 at 1:19 Comment(0)
A
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!

Antimonous answered 11/5, 2023 at 17:0 Comment(0)
M
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,
});
Marlenamarlene answered 23/2 at 17:59 Comment(0)
F
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",
      });
    },
  });
};


Fils answered 26/3 at 21:14 Comment(0)
S
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

Stylize answered 15/4 at 11:11 Comment(0)
A
0

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()

Auroraauroral answered 16/4 at 16:32 Comment(0)
P
0

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

Pursuit answered 14/5 at 19:36 Comment(1)
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.Klara
D
0

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

Davinadavine answered 8/6 at 16:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.