21

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

yoann84
  • 518
  • 1
  • 3
  • 14

13 Answers13

34

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.

Eliav Louski
  • 3,593
  • 2
  • 28
  • 52
29

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.

Yaroslav Draha
  • 455
  • 4
  • 12
13

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.

TkDodo
  • 20,449
  • 3
  • 50
  • 65
  • 1
    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. – yoann84 Jul 30 '21 at 11:45
6

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

Slav
  • 353
  • 4
  • 10
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();
Shogo Yahagi
  • 141
  • 1
  • 2
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'));
Samil Kahraman
  • 432
  • 6
  • 13
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

EasyGem
  • 111
  • 3
  • 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. – Sammy Mishal Jun 26 '23 at 18:48
2

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.
MasterPiece
  • 445
  • 7
  • 13
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>
Andres Pino
  • 155
  • 3
  • 9
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],
      });
    },
  });
}
jimmy
  • 109
  • 2
  • 9
0

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.

BertC
  • 2,243
  • 26
  • 33
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.

gregory batte
  • 101
  • 1
  • 3
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!

Awais Hussain
  • 1,501
  • 1
  • 14
  • 15