redux-toolkit: [question] - invalidate a result in cache when using "merge"
Description
I am trying to understand the right approach. Can you please give me an advice?
I am using a query
enpoint with merge
feature to “paginate” and keep all the results rendered on the page (clicking “next page” will display more results under those already displayed).
I use the merge
functionality to group all results together (along with forceRefetch
and serializeQueryArgs
). Which works. Now I want to be able to “remove” one of the results. Invalidating the cache (using invalidateTags
or even calling api.utils.invalidateTags
doesn’t have the desired effect. While the query IS triggered, the cache is NOT updated and the deleted result is still displayed.
In https://redux-toolkit.js.org/rtk-query/api/createApi#merge, I read: “no automatic structural sharing will be applied - it’s up to you to update the cache appropriately”
What’s the recommended way of achieving it? Reading the docs, I don’t see a way to achieve that. Read some SO answers about using entityAdapter
or using prevPage
, currentPage
and nextPage
, but it just sounds too complicated approach for such a simple functionality I am trying to achieve = delete a single result from the cache, identified by it’s ID
My expectation (sort of what I thought would happen) I assumed the result would be invalidated the moment I define
invalidateTags: ({id}) => [{type: "Result", id }] // just illustrates what I mean
Seems, when I opt for using merge
, this feature gets skipped
About this issue
- Original URL
- State: open
- Created 9 months ago
- Reactions: 9
- Comments: 25
@koukalp yeah, there’s a couple different sub-threads going on in this issue, which is also why I’m confused myself 😃
My first note is that the whole “use
merge
as an infinite query implementation” thing is, frankly, a hack. And I was the one who came up with it, and I knew it was a hack at the time, and it’s still a hack, and I don’t like it.I want us to ship a “real” infinite query implementation at some point. Ideally this year. I’ve got another thread that’s open to ask folks like you about specific use cases you would need it to handle, and I’d really appreciate it if you could leave some related thoughts over in that thread:
That said, it’ll also be at least a couple months before we finally have time to comb through the list of open RTKQ feature requests, do some prioritization and planning, and then actually start to work on them. So realistically I wouldn’t expect us to be able to ship something like that until at least the end of the year, and the
merge
approach is what we’ve got to live with for now.For your specific case: I don’t have enough context to understand what the actual technical issue is with “deleting an item” and “tag invalidation not working”. Could you provide an actual repo or CodeSandbox that shows this behavior? Right now there’s some comments in this thread that feel kind of vague describing possible behavior in apps, and I don’t know what folks are actually doing as far as configuration, or what specific behavior you’re seeing at runtime.
Also, what about tag invalidation is not working as expected?
@markerikson - I love the answer 😄
Essentially it answers my question - what I am doing is not really recommended / supported and therefore no surprise it doesn’t work like my imagination would like it to work 👍
I will try to prepare a representative case and that should answer all your questions.
Thanks!
@haleyngonadi - for me I need to delegate this to redux, where I can easily create reducers that either add (“onNextPageLoaded”) items or remove a single item (“onDeleteItem”) from the collection.
It just feels like a missed opportunity, bc
@markerikson - it feels the discussion went a bit off. My expectations are:
rtk-query
with caching andmerge
function to implement infinite scroll to view a list of itemsinvalidateTags
stops to work the moment I usemerge
and I don’t see a way to manipulate the cache to remove the (deleted) itemAs mentioned to @haleyngonadi above, the solution with using redux and a few reducers is quite straightforward, however then I pretty much opt out the
rtk-query
cache which feels like a missed opportunity@haleyngonadi : yes, although there’s two things that would need to change in that snippet:
cache
createEntityAdapter
specifically, you’d need to doreturn postAdapter.getInitialState()
(if the goal is to reset the data completely to empty), orreturn postAdapter.setAll(postAdapter.getInitialState(), response)
(to replace the data with the latest response contents)It seems a lot of people are having issues with this, and I have figured out how to make it work with the Entity Adapter, and it works great!
In react i manually set Page to 0 when needed to clear the cache
@markerikson Haha I completely understand. Thank you for the insight you’ve already provided 😃
There’s a couple tricky bits here:
merge
only gets called when there is already data in the cache entry. In other words,merge
will not be called for the first successful request (the cache entry is empty, there isn’t anything to “merge” into). It will be called for later successful responses (we already have data in the cache, there’s more data, now you can “merge” them). This is logical, but also confusing.transformResponse
and inmerge
, because in the initial request case onlytransformResponse
will be called, and you still need the data to be normalized.Going back to the question of “how do I replace the existing data?”, for the specific case you’ve got here, you’d want to use
postsAdapter.setAll(cache, response)
. The issue is how does that logic inmerge
know if it should do the “replace” vs the “update” handling. I don’t have an immediate answer for that, for multiple reasons: I don’t know if we do expose a way to know that intomerge
atm (ie, some argument that indicates “this was arefetch()
response” or something); I don’t know your specific codebase’s needs; and also I’m replying to this while doing real day-job work and only have limited brainpower available atm 😃Thank you for taking time to explain, @markerikson! I call that in
transformResponse
, is that the wrong way to do it?@markerikson Ahh, I see. Thank you. Would that still apply if I use
createEntityAdapter
?@haleyngonadi The
merge
callback is effectively a standard Immerproduce
function, which means that you can return a complete value instead of mutating the draft argument. So, you should be able to literallyreturn responseData
inside ofmerge
(instead of doing something likecachedData.push(newStuff)
, and it should replace the existing data in that cache entry.@markerikson A full refetch to happen. Pretty much go back to what it was when the user originally landed on the view.
Now that you mention it, I am getting back data with the request, but it doesn’t replace the cache. So the question indeed is how to replace the data already there!
I am dealing with a recommendation endpoint (no query args) that gives a different set of results every time it is called so I used the merge function to append the results for every call but when I invalidate the tag for this endpoint the request is made and results are added to the existing list rather than replacing.
can the merge function have the information that the tag is invalidated? then we put a check there