yoann84
yoann84

Reputation: 800

Invalidate queries doesn't work [React-Query]

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

Upvotes: 43

Views: 77738

Answers (22)

Luillyfe
Luillyfe

Reputation: 6842

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"] });

Upvotes: 2

tnemele12
tnemele12

Reputation: 1074

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

Upvotes: 3

RuslanNode
RuslanNode

Reputation: 1

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

Upvotes: 0

Berty
Berty

Reputation: 1178

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]);

Upvotes: 1

Eugene Koshelev
Eugene Koshelev

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

Upvotes: -1

Suleyman
Suleyman

Reputation: 252

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

Upvotes: 0

Rodrigo Mallmann
Rodrigo Mallmann

Reputation: 23

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",
      });
    },
  });
};


Upvotes: 0

Amil Aliyev
Amil Aliyev

Reputation: 31

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,
});

Upvotes: 0

Hendy Irawan
Hendy Irawan

Reputation: 21384

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

Upvotes: 6

MasterPiece
MasterPiece

Reputation: 564

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.

Upvotes: 9

Eliav Louski
Eliav Louski

Reputation: 5214

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.

Upvotes: 57

EasyGem
EasyGem

Reputation: 151

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

Upvotes: 2

Awais Hussain
Awais Hussain

Reputation: 1633

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!

Upvotes: 0

gregory batte
gregory batte

Reputation: 121

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.

Upvotes: 0

BertC
BertC

Reputation: 2656

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.

Upvotes: 1

jimmy
jimmy

Reputation: 148

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],
      });
    },
  });
}

Upvotes: 0

Yaroslav Draha
Yaroslav Draha

Reputation: 785

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.

Upvotes: 61

Andres Pino
Andres Pino

Reputation: 165

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>

Upvotes: 2

Slav
Slav

Reputation: 493

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

Upvotes: 11

Shogo Yahagi
Shogo Yahagi

Reputation: 171

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

Upvotes: 7

Samil Kahraman
Samil Kahraman

Reputation: 462

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'));

Upvotes: 2

TkDodo
TkDodo

Reputation: 28733

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.

Upvotes: 22

Related Questions