docs: selectInvalidedBy for multiple cache updates across queries#5226
docs: selectInvalidedBy for multiple cache updates across queries#5226bigcupcoffee wants to merge 10 commits into
selectInvalidedBy for multiple cache updates across queries#5226Conversation
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 11481f5:
|
✅ Deploy Preview for redux-starter-kit-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
@markerikson thoughts? |
selectInvalidedBy for multiple cache updates across queries
themavik
left a comment
There was a problem hiding this comment.
The selectInvalidatedBy walk plus per-endpoint updateQueryData is a solid pattern for fan-out cache patches. nit: the sample getNextPageParam stops when lastPage.length < 20 while initialPageParam is 1—readers copy-pasting might get pagination wrong unless the prose calls that out.
|
|
||
| The entity you want to update might be present in multiple differently shaped queries, such as | ||
| (infinite) lists, or even inside another entity. If you don't want to invalidate those instead, | ||
| you can avoid manually looping through every single cache entry by using [selectInvalidatedBy](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby): |
There was a problem hiding this comment.
| you can avoid manually looping through every single cache entry by using [selectInvalidatedBy](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby): | |
| you can avoid manually looping through every single cache entry by using [`selectInvalidatedBy`](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby): |
There was a problem hiding this comment.
Pull request overview
Adds documentation showing how to perform an optimistic cache update for a single entity that may appear across multiple RTK Query cache entries (detail query + infinite list), using api.util.selectInvalidatedBy to find affected entries.
Changes:
- Introduces a new “Updates Across Queries” section under Manual Cache Updates.
- Adds a TypeScript example demonstrating
selectInvalidatedBy+updateQueryDatafor both single-item and infinite-list query shapes.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const patches = entries.map(entry => { | ||
| switch (entry.endpointName) { | ||
| case 'getPost': | ||
| return dispatch( | ||
| api.util.updateQueryData( | ||
| entry.endpointName, | ||
| entry.originalArgs, | ||
| (draft) => { | ||
| Object.assign(draft, patch) | ||
| }, | ||
| ), | ||
| ) | ||
| case 'getPosts': | ||
| return dispatch( | ||
| api.util.updateQueryData( | ||
| entry.endpointName, | ||
| entry.originalArgs, | ||
| (draft) => { | ||
| draft.pages.forEach(page => { | ||
| page.forEach(entity => { | ||
| if (entity.id === id) Object.assign(entity, patch) | ||
| }) | ||
| }) | ||
| }, | ||
| ), | ||
| ) | ||
| default: | ||
| throw new Error(`Unknown endpoint (${entry.endpointName}) in updatePost's optimistic update`) | ||
| } | ||
| }) | ||
|
|
There was a problem hiding this comment.
Throwing an error here will crash the app during onQueryStarted if any other query provides the same tag but isn’t explicitly handled. For a docs example, it’s safer to ignore/skip unknown endpoints (or handle them with a no-op/log) so adding a new tagged query doesn’t introduce a runtime failure.
| const patches = entries.map(entry => { | |
| switch (entry.endpointName) { | |
| case 'getPost': | |
| return dispatch( | |
| api.util.updateQueryData( | |
| entry.endpointName, | |
| entry.originalArgs, | |
| (draft) => { | |
| Object.assign(draft, patch) | |
| }, | |
| ), | |
| ) | |
| case 'getPosts': | |
| return dispatch( | |
| api.util.updateQueryData( | |
| entry.endpointName, | |
| entry.originalArgs, | |
| (draft) => { | |
| draft.pages.forEach(page => { | |
| page.forEach(entity => { | |
| if (entity.id === id) Object.assign(entity, patch) | |
| }) | |
| }) | |
| }, | |
| ), | |
| ) | |
| default: | |
| throw new Error(`Unknown endpoint (${entry.endpointName}) in updatePost's optimistic update`) | |
| } | |
| }) | |
| const patches = entries.reduce((patches, entry) => { | |
| switch (entry.endpointName) { | |
| case 'getPost': | |
| patches.push( | |
| dispatch( | |
| api.util.updateQueryData( | |
| entry.endpointName, | |
| entry.originalArgs, | |
| (draft) => { | |
| Object.assign(draft, patch) | |
| }, | |
| ), | |
| ), | |
| ) | |
| break | |
| case 'getPosts': | |
| patches.push( | |
| dispatch( | |
| api.util.updateQueryData( | |
| entry.endpointName, | |
| entry.originalArgs, | |
| (draft) => { | |
| draft.pages.forEach(page => { | |
| page.forEach(entity => { | |
| if (entity.id === id) Object.assign(entity, patch) | |
| }) | |
| }) | |
| }, | |
| ), | |
| ), | |
| ) | |
| break | |
| default: | |
| break | |
| } | |
| return patches | |
| }, []) |
| } | ||
| }) | ||
|
|
||
| queryFulfilled.catch(() => patches.forEach(patch => patch.undo())) |
There was a problem hiding this comment.
In the rollback handler, the callback parameter name patch shadows the outer patch object from the mutation args. Renaming the inner variable (eg patchResult/patchAction) would make the example easier to follow.
| queryFulfilled.catch(() => patches.forEach(patch => patch.undo())) | |
| queryFulfilled.catch(() => patches.forEach((patchResult) => patchResult.undo())) |
|
|
||
| The entity you want to update might be present in multiple differently shaped queries, such as | ||
| (infinite) lists, or even inside another entity. If you don't want to invalidate those instead, | ||
| you can avoid manually looping through every single cache entry by using [selectInvalidatedBy](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby): |
There was a problem hiding this comment.
This link uses an absolute URL, while the rest of this page uses relative links to the local MDX docs (eg ../api/created-api/api-slice-utils.mdx#updatequerydata). Consider switching to a relative link to api-slice-utils.mdx#selectinvalidatedby so the docs work consistently in local/offline builds and across site base URLs.
| you can avoid manually looping through every single cache entry by using [selectInvalidatedBy](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby): | |
| you can avoid manually looping through every single cache entry by using [selectInvalidatedBy](../api/created-api/api-slice-utils.mdx#selectinvalidatedby): |
| ### Updates Across Queries | ||
|
|
||
| The entity you want to update might be present in multiple differently shaped queries, such as | ||
| (infinite) lists, or even inside another entity. If you don't want to invalidate those instead, |
There was a problem hiding this comment.
Wording is a bit unclear here: “invalidate those instead” reads like it refers to entities rather than queries. Consider rephrasing to “invalidate those queries instead” or “invalidate them instead” for clarity.
| (infinite) lists, or even inside another entity. If you don't want to invalidate those instead, | |
| (infinite) lists, or even inside another entity. If you don't want to invalidate those queries instead, |
This is a rough sample of what we use in our mobile app to update an entity across 10+ queries without invalidating them all
Switch cases are easier to extend IMO, while in our code we have some abstration layer for draft updaters and entity predicates (as we have secondary identifier plus nested entities), this is more for the devs to figure out if they feel like they need it
For some queries we even have updates outside of redux with an according custom undo return, so this also keeps the room for obscure custom cases (which I wish I didn't have to see & code)
Code definitely feels somewhat weird but I feel like we still need a mention of this usecase for
selectInvalidatedBy, for simplicity sake the code could only cover updating the list queries, but I figured I'd present a more complex case at this point, what do you think?Could probably use some touch up on the wording, feel free to comment 🙏