ReactQuery - useInfiniteQuery refetching issue
Asked Answered
A

1

13

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!

Alto answered 27/2, 2022 at 15:40 Comment(0)
S
13

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.

Svetlanasvoboda answered 1/3, 2022 at 14:34 Comment(8)
Thank you for taking the time to answer the question. My desire is to load only the first page. let's consider the following example. I have a query with the query key get-pages. I scroll to page 2, get page 2 results. I add a filter param, the query key gets one more filter=a. When I remove the filter, the query key goes back to get-pages and automatically gets page 1 and page 2 (because it's cached). My desire is to fetch only page 1. staleTime keeps the result which is not what I need.Alto
you have two pages in cache, so they will both be refetched. If you can live with a hard loading state, set cacheTime: 0. That will remove everything as soon as the key changes, and you'll start fresh when you go back. Other than that, you have to slice the pages and pageParams manually to only contain one element when the key changes, in a useEffect, with queryClient.setQueryDataSvetlanasvoboda
thanks @TkDodo! it actually resolved my issue. I found this answer very useful. Also, wrote some messages to the tw accScintilla
Any chance that you can share your approach, I'm bit confused. Implenting pagination + search, I'm struggling from quite some time.Grilled
@Svetlanasvoboda - what if, in this persons case, when you remove the "filter", you still have the "key" reference to the previous cache, why doens't it just return that rather than refetching those pages. For example... user goes thru two pages, his key, is something like ['myData', {}], then they add the filter, new results. and key is ['myData', {filter: 'a'}], once they remove the filter, should they get the cached results for ['myData', {}]? How do you achieve this?Diversity
if you remove the filter and you make the queryKey be ['myData', {}] you will get the cached data. it probably won't work with ['myData', {filter: undefined}] because that is slightly different :)Svetlanasvoboda
@Svetlanasvoboda thanks, this answered A LOT of questions i was having!! So you're saying if you're stepping through the API pages sequentially (1,2,3,etc) that you shouldn't be trying to remove pages from the cache? I'm using asyncstorage persister, so I thought as the user gets to page 5, I should be removing 1,2,3,4 as the user has already seen those images (and in my case probably won't view them again). From a storage perspective it seemed logical. Are you saying that is not possible or advisable? Or that if you do clean up that it will re-fetch those pages, or something else? ThanksGaslight
it's possible in v5 with the new maxPages option on infinite queriesSvetlanasvoboda

© 2022 - 2024 — McMap. All rights reserved.