Sabbin
Sabbin

Reputation: 2445

ReactQuery - useInfiniteQuery refetching issue

I have implemented infinite scroll on a project that is using React Query library.

So far so good. Everything works as expected using the useInfiniteScroll hook

One issue that I am encountering is the caching mechanism of this library. If I query for a resource, ex: GET /posts/, will trigger a GET /posts/?page=0, scroll a bit down, get the next results, GET /posts/?page=1 which is totally fine. If I add search parameter to the page will do a GET /posts/?filter=someValue&page=0. All fine... but when I remove the filter from the search it will automatically do GET /posts/?page=0 & GET /posts/?page=1

A solution is to remove the query from the cache using the remove() method from the query itself. But this means that I have to manually do it for each query.

I would like to get a better solution that will cover all cases. I have a queryWrapper where I want to handle this.

I tried using the queryClient instances invalidateQueries and resetQueries but none seems to be able to remove it from the cache...

In the examples below I keep a ref for the params, if they are changed I can trigger the query to reset via useLayoutEffect hook. This part works as expected

invalidateQueries attempt

queryClient.invalidateQueries(
    [queryKey, JSON.stringify(paramsRef.current)],
    {
      exact: true,
      refetchActive: false,
      refetchInactive: false
    },
    { cancelRefetch: true }
);

resetQueries attempt

queryClient
    .resetQueries([queryKey, JSON.stringify(paramsRef.current)], {
      refetchPage: (page, index) => index === 0
    })

I even tried the queryClient.clear() method which should remove all existing queries from the cache but still the page number somehow remains cached... I access the queryClient using useQueryClient hook. If I inspect it, I can see the queries inside.

Can someone please help me to sort this cache issue Thanks!

Upvotes: 13

Views: 19774

Answers (2)

Dmytro Bondarenko
Dmytro Bondarenko

Reputation: 1022

maxPages simply sets a page limit. The goal here is to reset the paging without invalidating the query client's cached data. For example: Here is a possible solution which you can use before calling the refetch():

queryClient.setQueryData([ModuleConstants.apiEndpoints.getGroupsEmployees, ...groupsHandles], () => ({
  pages: [{ items: [...data.slice(0, 30)] }],
  pageParams: [0],
}));

In my opinion, this logic should be placed within the hook's definition rather than the code that triggers the refetch function. Ideally, this could be controlled via a configuration option, such as resetPagingOnRefetch. In my opinion, it's a quite a rare case when you need to keep all the previously loaded pages for the same query key (I'm using a FB application's social feed as an example). I understand, that in some other cases this behavior is considered as "required".

Why should it be inside the hook and not in the UI code? The key reason is the use of queryKey: [ModuleConstants.apiEndpoints.getGroupsEmployees, ...groupsHandles]. Hardcoding this query key in two different places - in my opinion is considered as bad practice. The second reason is page size and the default page params. The third reason, if you providing a custom hook as a FC parameter (e.g. a completely different datasource, but it renders the same UI), now you have to deal with that spaghetti code.

Upvotes: 0

TkDodo
TkDodo

Reputation: 28833

but when I remove the filter from the search it will automatically do GET /posts/?page=0 & GET /posts/?page=1

This is the default react-query refetching behaviour: An infinite query only has one cache entry, with multiple pages. When a refetch happens, all currently visible pages will be refetched. This is documented here.

This is necessary for consistency, because if for example one entry was removed from page 1, and we wouldn't refetch page 2, the first entry on page 2 (old) would be equal to the last entry of page 1 (new).

Now the real question is, do you want a refetch or not when the filter changes? If not, then setting a staleTime would be the best solution. If you do want a refetch, refetching all pages is the safest option. Otherwise, you can try to remove all pages but the first one with queryClient.setQueryData when your query unmounts. react-query won't do that automatically because why would we delete data that the user has scrolled down to to see already.

Also note that for imperative refetches, like invalidateQueries, you have the option to pass a refetchPage function, where you can return a boolean for each page to indicate if it should be refetched or not. This is helpful if you only update one item and only want to refetch the page where this item resides. This is documented here.

Upvotes: 14

Related Questions