swr
swr copied to clipboard
Can not mutate data from SWRInfinite with `revalidateFirstPage: false` when key is not mounted
Bug report
I can not manually refresh data using mutate
when using SWRInfinite
and revalidateFirstPage: false
and key of that mutate is not bound to current page.
Description / Observed Behavior
I have a list of items, which I fetch using useSWRInfinite
. Since I do not want to fetch the first page everytime user fetches next page, I use it with revalidateFirstPage: false
option, and that works great. So, imagine I have a list of items: A, B, C, D, E. Each item have also its own details page. I can change item A
to Aa
and use mutate
when I am on list page to refresh cache without calling request again, and that also works. The problem is when I go to page A
and change that item's name to Aa
- even though I call exactly the same methods, when I go back to list page, the item is still A
, not Aa
. I tested it a lot of times, and that scenario works when I remove revalidateFirstPage: false
from useSWRInfinite
. But without it I have to load first page every time user scrolls down, which slows down loading new data...
Expected Behavior
Cache is always updated when using mutate
, but first page is not fetched everytime when I want to fetch n+1 page
Additional Context
SWR version 1.2.0
@tomaszczura Could you create a reproduced case of this?
I prepared something here: https://codesandbox.io/s/funny-driscoll-qrqwf?file=/src/App.js
'Reset cache' - this button calls mutate
for used swr infinite key
'Mutate cache' - this button calls mutate
for swr infinite key with data and without refetching
You can test it with the following steps:
- Make sure
revalidateFirstPage
isfalse
- Click 'Reset cache' and note the date displayed - assume it is
Mon Jan 31 2022 16:04:01 GMT+0100
- Click 'Show details' and then 'Hide details' - displayed date is still the same
- Click 'Mutate cache' - you should see 'TEST'
- Click 'Show details' and 'Hide details' - instead of 'TEST' you will see
Mon Jan 31 2022 16:04:01 GMT+0100
(same date as in step 2 Now repeat the step withrevalidateFirstPage: true
- you will see current date everytime - and that's my desired behavior. I do want to update the first page when I callmutate
- but not when I callsetSize
@koba04 any ideas? This bug blocks us from upgrading to 1.x
version
@tomaszczura I've tried your test case.
Click 'Show details' and then 'Hide details' - displayed date is still the same
At the step, the data has been updated because of revalidateOnMount: true
rather than being the same.
I might be wrong, but you should use revalidateIfStale: false
instead of revalidateOnMount: true
.
Could you try your example updated with the option?
@koba04 I created codesandbox which is closer to our case and with version 1.2.1
: https://codesandbox.io/s/distracted-chihiro-d682v?file=/src/App.js
Steps to reproduce:
- Open console (logs will be very helpful here)
- Click 'Show react query' - this simulates click on item in our list
- Click 'Mutate SWR Description'. You can see
Description updated to Updated description
in logs. This simluates mutating item which appears on this page and the prebious one. - Click 'Close react query'. This simulates going back to previous page. You can see two new lines in logs:
DATA Updated description
DATA React Hooks for Data Fetching
But without API Call
between them. So, the cache was updated in step 3, but after step 4, it was reverted to state from step 1. Where does this update come from? Also, no api call was made (there is a log when API is called).
I'd expect that when going to step 4, API call is made and cache value is the same as in step 3 until response from API comes.
I think that this may be related to fact, that our component which uses useSWRInfinite
is not unmounted and mounted again between steps 2/3/4 - we reuse the same component, just use another key
I guess your useMatchMutate
implementation updates cache data incorrectly, because SWR stores some metadata in addition to cache data, and useMatchMutate
seems to update the metadata not only cache data.
To avoid that, it's the best way to use mutate
returned from useSWRInfinite
if possible. SWR also provides unstable_serialize
that returns a cache key that SWR uses internally. But as the name implies, it's an unstable API.
We can not use mutate
returned from useSWRInfinite
, since we need a way to mutate our cache from different components in our code, even those which do not use useSWRInfinite
.
Also, we can not use unstable_serialize
, because we may not know the exact key - our real keys contain a lot of query params, and we want to modify all keys which contains some text (e.g. all with param type=image
).
We checked the cache, and key starting with $inf....
is always updated with our method, but this state is overwritten when going from point 3 to 4 (without any mutate or API call).
What we discover is that if I have keyA
for useSWRInfinite
, you create two entries incache
: one with keyA
and one with $inf$keyA
. Our useMatchMutate
updates the second one, but does not touch the first. This approach worked in version 1.0.0-beta.5
(and earlier), and it still works when revalidateFirstPage
is true
.
If that is not a valid way of updating cache, do you have any adice for us how to update cache, when we do not know the exact key and without access to bound mutate
?
@koba04 also running into this issue and was hoping for some clarification.
Here's a codesandbox that mixes @tomaszczura's example with the useSWRInfinite
example from the documentation. Its also using the same useMatchMutate
hook as described in swr's docs here.
My use case is that I have multiple lists using useSWRInfinite
and need a way to mutate data in them without needing to use the hook & its bound mutate because the components calling mutate don't need access to the data provided by the hook.
In the codesandbox you'll see I changed the example from the docs slightly to only use two keys for simplicity's sake. To reproduce the issue I'm running in to, you will:
- Load the app. This will fetch the first page of issues from the first repo.
- Click “Load Second Repo”. This will fetch the first page of issues from the second repo.
-
Click “Mutate Second Issue First Repo”. This will mutate the second issue in the cache for the first repo, changing the title to "MUTATED ISSUE". In the logs, you'll see that the
$inf$
cache corresponding to the first repo is updated correctly. -
Click “Load Initial Repo”. This will load the first repo data from the cache, including the mutation (you'll see in the logs that the very first
data
log after clicking the button includes the "MUTATED ISSUE" data. But, immediately afterdata
is changed to match its state before the mutation, wiping out the "MUTATED ISSUE" data.
Now, my confusion stems from what happens in 4. When we change the hooks key to the mutated data's key, it first loads the mutated data, but then replaces it with the initial data without ever hitting the API. My expectation was that it would either:
- Make a request to the API, compare the result to whats in the cache, and update the cache if the two aren't equal.
or
- Not make a request to the API and keep whats in the cache.
Neither seems to be happening, so I'm curious if you have any thoughts on how to best implement my use case and get around this issue in swr 1.2.1.
@koba04 I added one more thing to https://codesandbox.io/s/distracted-chihiro-d682v?file=/src/App.js. Now tere is another component a the bottow, which uses uswSWRInfinite
with key for swr repo. So, if you click 'Mutate SWR description' it will changes. Try to click 'Show react query' and click 'Mutate SWR description' - you will see it changes to 'Updated description'. But if you click 'Show react query', it goes back to description from API even if there was no API call, and that component has not been touched
@tomaszczura
you create two entries incache: one with keyA and one with $inf$keyA. Our useMatchMutate updates the second one, but does not touch the first. This approach worked in version 1.0.0-beta.5 (and earlier), and it still works when revalidateFirstPage is true.
I guess you have to update both data of the keys(keyA
and $inf$keyA
) because the stale data seems to be returned from the cache data of keyA
. If you also want to revalidate only a page of keyA, you would have to update context cache data of useSWRInfinite
. But I don't recommend those ways because these are implementation details and would be broken at any updates.
Currently, useSWRInfinite
doesn't provide an official way to update cache data of a specific page without the bounded mutate function. The best way to solve the problem is adding an API of mutate
function for useSWRInfinite
, so I think this issue is a feature request rather than a bug.
@koba04 Isn't really a way to change updates useSWRInfinite
with revalidateFirstPage: false
to work as it worked before version 1.0.1
? I mean, to update it everytime but not refetch when setSize
is used? For me it looks like a bug that I can modify cache and use it in two components, but when one of the components remounts, the cache is updated to the old one, so that both show incorrect state
@koba04 any updates on this?
We can not use
mutate
returned fromuseSWRInfinite
, since we need a way to mutate our cache from different components in our code, even those which do not useuseSWRInfinite
. Also, we can not useunstable_serialize
, because we may not know the exact key - our real keys contain a lot of query params, and we want to modify all keys which contains some text (e.g. all with paramtype=image
). We checked the cache, and key starting with$inf....
is always updated with our method, but this state is overwritten when going from point 3 to 4 (without any mutate or API call). What we discover is that if I havekeyA
foruseSWRInfinite
, you create two entries incache
: one withkeyA
and one with$inf$keyA
. OuruseMatchMutate
updates the second one, but does not touch the first. This approach worked in version1.0.0-beta.5
(and earlier), and it still works whenrevalidateFirstPage
istrue
.If that is not a valid way of updating cache, do you have any adice for us how to update cache, when we do not know the exact key and without access to bound
mutate
?
we have same use case on our end, any updates?
@condinoaljoseph we migrated to react-query
I have the same issue, if we put revalidateFirstPage: false
we can't mutate the keys cached, this has to be a bug, because why i can't mutate all the pages from swrInfinite
when that option is in false
but i can o true
@condinoaljoseph we migrated to react-query
Is there no issue like this in react query? We're considering mirgration to react query.
We should consider it as a part of #2497
@condinoaljoseph we migrated to react-query
Is there no issue like this in react query? We're considering mirgration to react query.
we also migrated to react query and its working great for us
nope, you can invalidate infinite cache as simply as "normal" cache