14

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!

2 Answers 2

15

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.

Sign up to request clarification or add additional context in comments.

9 Comments

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.
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.setQueryData
thanks @TkDodo! it actually resolved my issue. I found this answer very useful. Also, wrote some messages to the tw acc
Any chance that you can share your approach, I'm bit confused. Implenting pagination + search, I'm struggling from quite some time.
it's possible in v5 with the new maxPages option on infinite queries
|
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.