From c0d0292296cd3f28f4ecc8690d06654cd4f1875e Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:06:48 +0200 Subject: [PATCH 1/9] docs: selectInvalidedBy for multiple cache updates across queries --- docs/rtk-query/usage/manual-cache-updates.mdx | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index 7f05b88a1e..73e006e853 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -224,6 +224,104 @@ const api = createApi({ }) ``` +### Updating lists + +The entity you want to update might be present in multiple differently shaped queries, such as +(infinite) lists, or even inside another entity. In those cases you can avoid manually looping +through every single cache entry by using [selectByInvalidatedBy](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby) util: + +```ts title="Optimistic update mutation example (async await)" +// file: types.ts noEmit +export interface Post { + id: number + name: string +} + +// file: api.ts +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' +import type { Post } from './types' + +const api = createApi({ + baseQuery: fetchBaseQuery({ + baseUrl: '/', + }), + tagTypes: ['Post'], + endpoints: (build) => ({ + getPost: build.query({ + query: (id) => `post/${id}`, + // highlight-start + providesTags: (result: Post) => result ? [{ type: 'Post', id: result.id }] : [], + // highlight-end + }), + getPosts: builder.infiniteQuery({ + query: ({ pageParam }) => `posts/${pageParam}`, + // highlight-start + providesTags: (result: Post[]) => { + if (!result) return [] + + return result.map(post => ({ type: 'Post', id: post.id })) + }, + // highlight-end + infiniteQueryOptions: { + initialPageParam: 1, + getNextPageParam: (lastPage, _, lastPageParam) => + lastPage.length < 20 ? undefined : lastPageParam + 1, + }, + }), + updatePost: build.mutation & Partial>({ + query: ({ id, ...patch }) => ({ + url: `post/${id}`, + method: 'PATCH', + body: patch, + }), + // highlight-start + onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled, getState }) { + const entries = api.util.selectInvalidatedBy(getState(), [{ type: 'Post', id }]) + + // the structure is somewhat questionable but it allows to add similar queries + // much easier, for example adding 'getUserPosts', 'getLikedPosts' would be just + // + case 'getUserPosts': + // as it's likely to also be an infinite query with the same response schema + + 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: + // either throw an error or ignore silently providing an empty undo? + } + }) + + queryFulfilled.catch(() => patches.forEach(patch => patch.undo())) + }, + // highlight-end + }), + }), +}) +``` + ### General Updates If you find yourself wanting to update cache data elsewhere in your application, you can do so From b09e96769e04f138ca4c7b5a06eed6eb039bb18e Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:14:14 +0200 Subject: [PATCH 2/9] update title --- docs/rtk-query/usage/manual-cache-updates.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index 73e006e853..00db5f94b6 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -230,7 +230,7 @@ The entity you want to update might be present in multiple differently shaped qu (infinite) lists, or even inside another entity. In those cases you can avoid manually looping through every single cache entry by using [selectByInvalidatedBy](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby) util: -```ts title="Optimistic update mutation example (async await)" +```ts title="Optimistic update mutation example (promise.catch, across queries)" // file: types.ts noEmit export interface Post { id: number From e61d33d2de1324d8c823a33e6ac982f419fdb548 Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:16:58 +0200 Subject: [PATCH 3/9] mention invalidation as primary approach --- docs/rtk-query/usage/manual-cache-updates.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index 00db5f94b6..161b60db1b 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -227,8 +227,8 @@ const api = createApi({ ### Updating lists The entity you want to update might be present in multiple differently shaped queries, such as -(infinite) lists, or even inside another entity. In those cases you can avoid manually looping -through every single cache entry by using [selectByInvalidatedBy](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby) util: +(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 [selectByInvalidatedBy](https://redux-toolkit.js.org/rtk-query/api/created-api/api-slice-utils#selectinvalidatedby): ```ts title="Optimistic update mutation example (promise.catch, across queries)" // file: types.ts noEmit From 905050e5951c5a6e4125a48825ff574cc9fdc47b Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:36:06 +0200 Subject: [PATCH 4/9] is this a better title? --- docs/rtk-query/usage/manual-cache-updates.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index 161b60db1b..734055e395 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -224,7 +224,7 @@ const api = createApi({ }) ``` -### Updating lists +### 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, From 460d080edd2cde032c8f523c1cf3d09aa69b83bd Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:40:33 +0200 Subject: [PATCH 5/9] fix ts --- docs/rtk-query/usage/manual-cache-updates.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index 734055e395..a198ad39e9 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -250,13 +250,13 @@ const api = createApi({ getPost: build.query({ query: (id) => `post/${id}`, // highlight-start - providesTags: (result: Post) => result ? [{ type: 'Post', id: result.id }] : [], + providesTags: (result) => result ? [{ type: 'Post', id: result.id }] : [], // highlight-end }), getPosts: builder.infiniteQuery({ query: ({ pageParam }) => `posts/${pageParam}`, // highlight-start - providesTags: (result: Post[]) => { + providesTags: (result) => { if (!result) return [] return result.map(post => ({ type: 'Post', id: post.id })) From c9688138830132625d9348614f52a46890c4fbec Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:44:33 +0200 Subject: [PATCH 6/9] Fix typo in build.* --- docs/rtk-query/usage/manual-cache-updates.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index a198ad39e9..04d1e534e0 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -253,7 +253,7 @@ const api = createApi({ providesTags: (result) => result ? [{ type: 'Post', id: result.id }] : [], // highlight-end }), - getPosts: builder.infiniteQuery({ + getPosts: build.infiniteQuery({ query: ({ pageParam }) => `posts/${pageParam}`, // highlight-start providesTags: (result) => { From 60eac6eda1a5537f14685fd4f104aa13725d1605 Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:51:12 +0200 Subject: [PATCH 7/9] Fix infinite list providesTags --- docs/rtk-query/usage/manual-cache-updates.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index 04d1e534e0..6b0debb1cb 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -259,7 +259,7 @@ const api = createApi({ providesTags: (result) => { if (!result) return [] - return result.map(post => ({ type: 'Post', id: post.id })) + return result.pages.flatMap(page => page.map(post => ({ type: 'Post', id: post.id }))) }, // highlight-end infiniteQueryOptions: { From 1a9d2eef9dd5f3dbae757799d50830bb9e6fb9cb Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 02:01:16 +0200 Subject: [PATCH 8/9] Fix ts --- docs/rtk-query/usage/manual-cache-updates.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index 6b0debb1cb..128a5be819 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -310,7 +310,7 @@ const api = createApi({ ), ) default: - // either throw an error or ignore silently providing an empty undo? + throw new Error(`Unknown endpoint (${entry.endpointName}) in updatePost's optimistic update`) } }) From c7f566238fa1dd55eb646e50e27e108711889b78 Mon Sep 17 00:00:00 2001 From: bigcupcoffee <24373206+bigcupcoffee@users.noreply.github.com> Date: Wed, 18 Feb 2026 02:05:51 +0200 Subject: [PATCH 9/9] Fix typo --- docs/rtk-query/usage/manual-cache-updates.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rtk-query/usage/manual-cache-updates.mdx b/docs/rtk-query/usage/manual-cache-updates.mdx index 128a5be819..045e40173f 100644 --- a/docs/rtk-query/usage/manual-cache-updates.mdx +++ b/docs/rtk-query/usage/manual-cache-updates.mdx @@ -228,7 +228,7 @@ const api = createApi({ 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 [selectByInvalidatedBy](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): ```ts title="Optimistic update mutation example (promise.catch, across queries)" // file: types.ts noEmit