From a7fd57c9187d2e7c57b1ba124b0376f6fcbb89e7 Mon Sep 17 00:00:00 2001 From: Lenz Weber Date: Sat, 2 Jul 2022 23:06:19 +0200 Subject: [PATCH 1/3] scope `getRunningOperationPromise` to store instance --- .../toolkit/src/query/core/buildInitiate.ts | 88 ++++++++++++------- packages/toolkit/src/query/core/module.ts | 23 +++-- 2 files changed, 74 insertions(+), 37 deletions(-) diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index f29514cbdc..8b9a356d77 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -14,6 +14,7 @@ import type { Api, ApiContext } from '../apiTypes' import type { ApiEndpointQuery } from './module' import type { BaseQueryError, QueryReturnValue } from '../baseQueryTypes' import type { QueryResultSelectorResult } from './buildSelectors' +import { Dispatch } from 'redux' declare module './module' { export interface ApiEndpointQuery< @@ -196,14 +197,14 @@ export function buildInitiate({ api: Api context: ApiContext }) { - const runningQueries: Record< - string, - QueryActionCreatorResult | undefined - > = {} - const runningMutations: Record< - string, - MutationActionCreatorResult | undefined - > = {} + const runningQueries: Map< + Dispatch, + Record | undefined> + > = new Map() + const runningMutations: Map< + Dispatch, + Record | undefined> + > = new Map() const { unsubscribeQueryResult, @@ -220,25 +221,33 @@ export function buildInitiate({ function getRunningOperationPromise( endpointName: string, argOrRequestId: any - ): any { - const endpointDefinition = context.endpointDefinitions[endpointName] - if (endpointDefinition.type === DefinitionType.query) { - const queryCacheKey = serializeQueryArgs({ - queryArgs: argOrRequestId, - endpointDefinition, - endpointName, - }) - return runningQueries[queryCacheKey] - } else { - return runningMutations[argOrRequestId] + ): ThunkAction { + return (dispatch: Dispatch) => { + const endpointDefinition = context.endpointDefinitions[endpointName] + if (endpointDefinition.type === DefinitionType.query) { + const queryCacheKey = serializeQueryArgs({ + queryArgs: argOrRequestId, + endpointDefinition, + endpointName, + }) + return runningQueries.get(dispatch)?.[queryCacheKey] + } else { + return runningMutations.get(dispatch)?.[argOrRequestId] + } } } - function getRunningOperationPromises() { - return [ - ...Object.values(runningQueries), - ...Object.values(runningMutations), - ].filter((t: T | undefined): t is T => !!t) + function getRunningOperationPromises(): ThunkAction< + Promise[], + any, + any, + AnyAction + > { + return (dispatch: Dispatch) => + [ + ...Object.values(runningQueries.get(dispatch) || {}), + ...Object.values(runningMutations.get(dispatch) || {}), + ].filter((t: T | undefined): t is T => !!t) } function middlewareWarning(getState: () => RootState<{}, string, string>) { @@ -302,7 +311,7 @@ Features like automatic cache collection, automatic refetching etc. will not be const skippedSynchronously = stateAfter.requestId !== requestId - const runningQuery = runningQueries[queryCacheKey] + const runningQuery = runningQueries.get(dispatch)?.[queryCacheKey] const selectFromState = () => selector(getState()) const statePromise: QueryActionCreatorResult = Object.assign( @@ -360,9 +369,15 @@ Features like automatic cache collection, automatic refetching etc. will not be ) if (!runningQuery && !skippedSynchronously && !forceQueryFn) { - runningQueries[queryCacheKey] = statePromise + const running = runningQueries.get(dispatch) || {} + running[queryCacheKey] = statePromise + runningQueries.set(dispatch, running) + statePromise.then(() => { - delete runningQueries[queryCacheKey] + delete running[queryCacheKey] + if (!Object.keys(running).length) { + runningQueries.delete(dispatch) + } }) } @@ -404,15 +419,24 @@ Features like automatic cache collection, automatic refetching etc. will not be reset, }) - runningMutations[requestId] = ret + const running = runningMutations.get(dispatch) || {} + runningMutations.set(dispatch, running) + running[requestId] = ret ret.then(() => { - delete runningMutations[requestId] + delete running[requestId] + if (!Object.keys(running).length) { + runningMutations.delete(dispatch) + } }) if (fixedCacheKey) { - runningMutations[fixedCacheKey] = ret + running[fixedCacheKey] = ret ret.then(() => { - if (runningMutations[fixedCacheKey] === ret) - delete runningMutations[fixedCacheKey] + if (running[fixedCacheKey] === ret) { + delete running[fixedCacheKey] + if (!Object.keys(running).length) { + runningMutations.delete(dispatch) + } + } }) } diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 94b0ac3be5..527498c5c0 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -142,7 +142,12 @@ declare module '../apiTypes' { * Useful for SSR scenarios to await everything triggered in any way, * including via hook calls, or manually dispatching `initiate` actions. */ - getRunningOperationPromises: () => Array> + getRunningOperationPromises: () => ThunkAction< + Array>, + any, + any, + AnyAction + > /** * If a promise is running for a given endpoint name + argument combination, * returns that promise. Otherwise, returns `undefined`. @@ -152,21 +157,29 @@ declare module '../apiTypes' { getRunningOperationPromise>( endpointName: EndpointName, args: QueryArgFrom - ): + ): ThunkAction< | QueryActionCreatorResult< Definitions[EndpointName] & { type: 'query' } > - | undefined + | undefined, + any, + any, + AnyAction + > getRunningOperationPromise< EndpointName extends MutationKeys >( endpointName: EndpointName, fixedCacheKeyOrRequestId: string - ): + ): ThunkAction< | MutationActionCreatorResult< Definitions[EndpointName] & { type: 'mutation' } > - | undefined + | undefined, + any, + any, + AnyAction + > /** * A Redux thunk that can be used to manually trigger pre-fetching of data. From 565f59783756821c8f7e726d4ede6564263c277c Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Sat, 15 Oct 2022 11:51:14 +0200 Subject: [PATCH 2/3] replace `getRunningOperationPromise(s)` with `getRunning(Query|Queries|Mutation|Mutations)Thunk` --- .../api/created-api/api-slice-utils.mdx | 48 +++---- docs/rtk-query/api/created-api/overview.mdx | 31 +++-- .../rtk-query/usage/server-side-rendering.mdx | 4 +- .../toolkit/src/query/core/buildInitiate.ts | 67 ++++++---- packages/toolkit/src/query/core/module.ts | 117 +++++++++++++----- 5 files changed, 175 insertions(+), 92 deletions(-) diff --git a/docs/rtk-query/api/created-api/api-slice-utils.mdx b/docs/rtk-query/api/created-api/api-slice-utils.mdx index 888d916c61..6042762679 100644 --- a/docs/rtk-query/api/created-api/api-slice-utils.mdx +++ b/docs/rtk-query/api/created-api/api-slice-utils.mdx @@ -249,53 +249,59 @@ Note that [hooks](./hooks.mdx) also track state in local component state and mig dispatch(api.util.resetApiState()) ``` -## `getRunningOperationPromises` +## `getRunningQueriesThunk` and `getRunningMutationsThunk` #### Signature ```ts no-transpile -getRunningOperationPromises: () => Array> +getRunningQueriesThunk(): ThunkWithReturnValue>> +getRunningMutationsThunk(): ThunkWithReturnValue>> ``` #### Description -A function that returns all promises for running queries and mutations. +Thunks that (if dispatched) return either all running queries or mutations. +These returned values can be awaited like promises. -This is useful for SSR scenarios to await everything triggered in any way, including via hook calls, +This is useful for SSR scenarios to await all queries (or mutations) triggered in any way, including via hook calls or manually dispatching `initiate` actions. -```ts no-transpile title="Awaiting all currently running queries & mutations example" -await Promise.all(api.util.getRunningOperationPromises()) +```ts no-transpile title="Awaiting all currently running queries example" +await Promise.all(dispatch(api.util.getRunningQueriesThunk())) ``` -## `getRunningOperationPromise` +## `getRunningQueryThunk` and `getRunningMutationThunk` #### Signature ```ts no-transpile -getRunningOperationPromise: >( +getRunningQueryThunk>( endpointName: EndpointName, args: QueryArgFrom -) => - | QueryActionCreatorResult +): ThunkWithReturnValue< + | QueryActionCreatorResult< + Definitions[EndpointName] & { type: 'query' } + > | undefined +> -getRunningOperationPromise: >( +getRunningMutationThunk>( endpointName: EndpointName, fixedCacheKeyOrRequestId: string -) => - | MutationActionCreatorResult +): ThunkWithReturnValue< + | MutationActionCreatorResult< + Definitions[EndpointName] & { type: 'mutation' } + > | undefined +> ``` #### Description -A function that returns a single promise for a given endpoint name + argument combination, -if it is currently running. If it is not currently running, the function returns `undefined`. +Thunks that (if dispatched) return a single running query (or mutation) for a given +endpoint name + argument (or requestId/fixedCacheKey) combination, if it is currently running. +If it is not currently running, the function returns `undefined`. -When used with mutation endpoints, it accepts a [fixed cache key](./hooks.mdx#signature-1) -or request ID rather than the argument. - -This is primarily added to add experimental support for suspense in the future. -It enables writing custom hooks that look up if RTK Query has already got a running promise -for a certain endpoint/argument combination, and retrieving that promise to `throw` it. +These thunks are primarily added to add experimental support for suspense in the future. +They enable writing custom hooks that look up if RTK Query has already got a running query/mutation +for a certain endpoint/argument combination, and retrieving that to `throw` it as a promise. diff --git a/docs/rtk-query/api/created-api/overview.mdx b/docs/rtk-query/api/created-api/overview.mdx index 2ce19fe0d4..c64dd64b72 100644 --- a/docs/rtk-query/api/created-api/overview.mdx +++ b/docs/rtk-query/api/created-api/overview.mdx @@ -53,20 +53,29 @@ type Api = { Array>, string > - resetApiState: SliceActions['resetApiState'] - getRunningOperationPromises: () => Array> - getRunningOperationPromise: >( + selectInvalidatedBy: ( + state: FullState, + tags: Array> + ) => Array<{ + endpointName: string + originalArgs: any + queryCacheKey: string + }> + resetApiState: ActionCreator + getRunningQueryThunk( endpointName: EndpointName, - args: QueryArgFrom - ) => - | QueryActionCreatorResult - | undefined - getRunningOperationPromise: >( + args: QueryArg + ): ThunkWithReturnValue + getRunningMutationThunk( endpointName: EndpointName, fixedCacheKeyOrRequestId: string - ) => - | MutationActionCreatorResult - | undefined + ): ThunkWithReturnValue + getRunningQueriesThunk(): ThunkWithReturnValue< + Array> + > + getRunningMutationsThunk(): ThunkWithReturnValue< + Array> + > } // Internal actions diff --git a/docs/rtk-query/usage/server-side-rendering.mdx b/docs/rtk-query/usage/server-side-rendering.mdx index 94ee03403e..4f5da85db2 100644 --- a/docs/rtk-query/usage/server-side-rendering.mdx +++ b/docs/rtk-query/usage/server-side-rendering.mdx @@ -21,7 +21,7 @@ The workflow is as follows: - Set up `next-redux-wrapper` - In `getStaticProps` or `getServerSideProps`: - Pre-fetch all queries via the `initiate` actions, e.g. `store.dispatch(api.endpoints.getPokemonByName.initiate(name))` - - Wait for each query to finish using `await Promise.all(api.util.getRunningOperationPromises())` + - Wait for each query to finish using `await Promise.all(dispatch(api.util.getRunningQueriesThunk()))` - In your `createApi` call, configure rehydration using the `extractRehydrationInfo` option: [examples](docblock://query/createApi.ts?token=CreateApiOptions.extractRehydrationInfo) @@ -56,4 +56,4 @@ The workflow is as follows: [examples](docblock://query/react/module.ts?token=ReactHooksModuleOptions.unstable__sideEffectsInRender) - Use your custom `createApi` when calling `const api = createApi({...})` -- Wait for all queries to finish using `await Promise.all(api.util.getRunningOperationPromises())` before performing the next render cycle +- Wait for all queries to finish using `await Promise.all(dispatch(api.util.getRunningQueriesThunk()))` before performing the next render cycle diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 8b9a356d77..69b54ed49e 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -14,7 +14,7 @@ import type { Api, ApiContext } from '../apiTypes' import type { ApiEndpointQuery } from './module' import type { BaseQueryError, QueryReturnValue } from '../baseQueryTypes' import type { QueryResultSelectorResult } from './buildSelectors' -import { Dispatch } from 'redux' +import type { Dispatch } from 'redux' declare module './module' { export interface ApiEndpointQuery< @@ -214,40 +214,53 @@ export function buildInitiate({ return { buildInitiateQuery, buildInitiateMutation, - getRunningOperationPromises, - getRunningOperationPromise, + getRunningQueryThunk, + getRunningMutationThunk, + getRunningQueriesThunk, + getRunningMutationsThunk, } - function getRunningOperationPromise( - endpointName: string, - argOrRequestId: any - ): ThunkAction { + function getRunningQueryThunk(endpointName: string, queryArgs: any) { return (dispatch: Dispatch) => { const endpointDefinition = context.endpointDefinitions[endpointName] - if (endpointDefinition.type === DefinitionType.query) { - const queryCacheKey = serializeQueryArgs({ - queryArgs: argOrRequestId, - endpointDefinition, - endpointName, - }) - return runningQueries.get(dispatch)?.[queryCacheKey] - } else { - return runningMutations.get(dispatch)?.[argOrRequestId] - } + const queryCacheKey = serializeQueryArgs({ + queryArgs, + endpointDefinition, + endpointName, + }) + return runningQueries.get(dispatch)?.[queryCacheKey] as + | QueryActionCreatorResult + | undefined } } - function getRunningOperationPromises(): ThunkAction< - Promise[], - any, - any, - AnyAction - > { + function getRunningMutationThunk( + /** + * this is only here to allow TS to infer the result type by input value + * we could use it to validate the result, but it's probably not necessary + */ + _endpointName: string, + fixedCacheKeyOrRequestId: string + ) { + return (dispatch: Dispatch) => { + return runningMutations.get(dispatch)?.[fixedCacheKeyOrRequestId] as + | MutationActionCreatorResult + | undefined + } + } + + function getRunningQueriesThunk() { + return (dispatch: Dispatch) => + Object.values(runningQueries.get(dispatch) || {}).filter( + (t: T | undefined): t is T => !!t + ) + } + + function getRunningMutationsThunk() { return (dispatch: Dispatch) => - [ - ...Object.values(runningQueries.get(dispatch) || {}), - ...Object.values(runningMutations.get(dispatch) || {}), - ].filter((t: T | undefined): t is T => !!t) + Object.values(runningMutations.get(dispatch) || {}).filter( + (t: T | undefined): t is T => !!t + ) } function middlewareWarning(getState: () => RootState<{}, string, string>) { diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 527498c5c0..0402a120ca 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -71,6 +71,9 @@ export type CoreModule = | ReferenceQueryLifecycle | ReferenceCacheCollection +interface ThunkWithReturnValue + extends ThunkAction {} + declare module '../apiTypes' { export interface ApiModules< // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -138,47 +141,81 @@ declare module '../apiTypes' { */ util: { /** - * Returns all promises for running queries and mutations. - * Useful for SSR scenarios to await everything triggered in any way, - * including via hook calls, or manually dispatching `initiate` actions. + * This method had to be removed due to a conceptual bug in RTK. + * Please see https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. + * @deprecated */ - getRunningOperationPromises: () => ThunkAction< - Array>, - any, - any, - AnyAction - > + getRunningOperationPromises: never // this is now types as `never` to immediately throw TS errors on use, but still allow for a comment + /** - * If a promise is running for a given endpoint name + argument combination, - * returns that promise. Otherwise, returns `undefined`. - * Can be used to await a specific query/mutation triggered in any way, - * including via hook calls, or manually dispatching `initiate` actions. + * This method had to be removed due to a conceptual bug in RTK. + * Please see https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. + * @deprecated + */ + getRunningOperationPromise: never // this is now types as `never` to immediately throw TS errors on use, but still allow for a comment + + /** + * A thunk that (if dispatched) will return a specific running query, identified + * by `endpointName` and `args`. + * If that query is not running, dispatching the thunk will result in `undefined`. + * + * Can be used to await a specific query triggered in any way, + * including via hook calls or manually dispatching `initiate` actions. + * + * See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. */ - getRunningOperationPromise>( + getRunningQueryThunk>( endpointName: EndpointName, args: QueryArgFrom - ): ThunkAction< + ): ThunkWithReturnValue< | QueryActionCreatorResult< Definitions[EndpointName] & { type: 'query' } > - | undefined, - any, - any, - AnyAction + | undefined > - getRunningOperationPromise< - EndpointName extends MutationKeys - >( + + /** + * A thunk that (if dispatched) will return a specific running mutation, identified + * by `endpointName` and `fixedCacheKey` or `requestId`. + * If that mutation is not running, dispatching the thunk will result in `undefined`. + * + * Can be used to await a specific mutation triggered in any way, + * including via hook trigger functions or manually dispatching `initiate` actions. + * + * See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. + */ + getRunningMutationThunk>( endpointName: EndpointName, fixedCacheKeyOrRequestId: string - ): ThunkAction< + ): ThunkWithReturnValue< | MutationActionCreatorResult< Definitions[EndpointName] & { type: 'mutation' } > - | undefined, - any, - any, - AnyAction + | undefined + > + + /** + * A thunk that (if dispatched) will return all running queries. + * + * Useful for SSR scenarios to await all running queries triggered in any way, + * including via hook calls or manually dispatching `initiate` actions. + * + * See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. + */ + getRunningQueriesThunk(): ThunkWithReturnValue< + Array> + > + + /** + * A thunk that (if dispatched) will return all running mutations. + * + * Useful for SSR scenarios to await all running mutations triggered in any way, + * including via hook calls or manually dispatching `initiate` actions. + * + * See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. + */ + getRunningMutationsThunk(): ThunkWithReturnValue< + Array> > /** @@ -307,6 +344,11 @@ declare module '../apiTypes' { string > + /** + * A function to select all `{ endpointName, originalArgs, queryCacheKey }` combinations that would be invalidated by a specific set of tags. + * + * Can be used for mutations that want to do optimistic updates instead of invalidating a set of tags, but don't know exactly what they need to update. + */ selectInvalidatedBy: ( state: RootState, tags: ReadonlyArray> @@ -495,8 +537,10 @@ export const coreModule = (): Module => ({ const { buildInitiateQuery, buildInitiateMutation, - getRunningOperationPromises, - getRunningOperationPromise, + getRunningMutationThunk, + getRunningMutationsThunk, + getRunningQueriesThunk, + getRunningQueryThunk, } = buildInitiate({ queryThunk, mutationThunk, @@ -505,9 +549,20 @@ export const coreModule = (): Module => ({ context, }) + function removedSSRHelper(): never { + throw new Error( + `This method had to be removed due to a conceptual bug in RTK. + Please see https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details.` + ) + } + safeAssign(api.util, { - getRunningOperationPromises, - getRunningOperationPromise, + getRunningOperationPromises: removedSSRHelper as any, + getRunningOperationPromise: removedSSRHelper as any, + getRunningMutationThunk, + getRunningMutationsThunk, + getRunningQueryThunk, + getRunningQueriesThunk, }) return { From a73aee9f2c1cb23d00a45f3a9114570718e2aacc Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Sat, 15 Oct 2022 17:28:07 +0200 Subject: [PATCH 3/3] re-add faulty `getRunningOperationPromises` but have it throw in development --- .../toolkit/src/query/core/buildInitiate.ts | 40 ++++++++++-- packages/toolkit/src/query/core/module.ts | 25 ++++---- .../toolkit/src/query/utils/isNotNullish.ts | 3 + .../injectableCombineReducers.example.ts | 63 +++++++++++++++++++ 4 files changed, 114 insertions(+), 17 deletions(-) create mode 100644 packages/toolkit/src/query/utils/isNotNullish.ts create mode 100644 packages/toolkit/src/tests/injectableCombineReducers.example.ts diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 69b54ed49e..c4f6d55371 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -15,6 +15,7 @@ import type { ApiEndpointQuery } from './module' import type { BaseQueryError, QueryReturnValue } from '../baseQueryTypes' import type { QueryResultSelectorResult } from './buildSelectors' import type { Dispatch } from 'redux' +import { isNotNullish } from '../utils/isNotNullish' declare module './module' { export interface ApiEndpointQuery< @@ -218,6 +219,37 @@ export function buildInitiate({ getRunningMutationThunk, getRunningQueriesThunk, getRunningMutationsThunk, + getRunningOperationPromises, + removalWarning, + } + + /** @deprecated to be removed in 2.0 */ + function removalWarning(): never { + throw new Error( + `This method had to be removed due to a conceptual bug in RTK. + Please see https://github.com/reduxjs/redux-toolkit/pull/2481 for details. + See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for new guidance on SSR.` + ) + } + + /** @deprecated to be removed in 2.0 */ + function getRunningOperationPromises() { + if ( + typeof process !== 'undefined' && + process.env.NODE_ENV === 'development' + ) { + removalWarning() + } else { + const extract = ( + v: Map, Record> + ) => + Array.from(v.values()).flatMap((queriesForStore) => + queriesForStore ? Object.values(queriesForStore) : [] + ) + return [...extract(runningQueries), ...extract(runningMutations)].filter( + isNotNullish + ) + } } function getRunningQueryThunk(endpointName: string, queryArgs: any) { @@ -251,16 +283,12 @@ export function buildInitiate({ function getRunningQueriesThunk() { return (dispatch: Dispatch) => - Object.values(runningQueries.get(dispatch) || {}).filter( - (t: T | undefined): t is T => !!t - ) + Object.values(runningQueries.get(dispatch) || {}).filter(isNotNullish) } function getRunningMutationsThunk() { return (dispatch: Dispatch) => - Object.values(runningMutations.get(dispatch) || {}).filter( - (t: T | undefined): t is T => !!t - ) + Object.values(runningMutations.get(dispatch) || {}).filter(isNotNullish) } function middlewareWarning(getState: () => RootState<{}, string, string>) { diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 0402a120ca..9950f31493 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -142,14 +142,22 @@ declare module '../apiTypes' { util: { /** * This method had to be removed due to a conceptual bug in RTK. - * Please see https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. + * + * Despite TypeScript errors, it will continue working in the "buggy" way it did + * before in production builds and will be removed in the next major release. + * + * Nonetheless, you should immediately replace it with the new recommended approach. + * See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for new guidance on SSR. + * + * Please see https://github.com/reduxjs/redux-toolkit/pull/2481 for details. * @deprecated */ getRunningOperationPromises: never // this is now types as `never` to immediately throw TS errors on use, but still allow for a comment /** * This method had to be removed due to a conceptual bug in RTK. - * Please see https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. + * It has been replaced by `api.util.getRunningQueryThunk` and `api.util.getRunningMutationThunk`. + * Please see https://github.com/reduxjs/redux-toolkit/pull/2481 for details. * @deprecated */ getRunningOperationPromise: never // this is now types as `never` to immediately throw TS errors on use, but still allow for a comment @@ -541,6 +549,8 @@ export const coreModule = (): Module => ({ getRunningMutationsThunk, getRunningQueriesThunk, getRunningQueryThunk, + getRunningOperationPromises, + removalWarning, } = buildInitiate({ queryThunk, mutationThunk, @@ -549,16 +559,9 @@ export const coreModule = (): Module => ({ context, }) - function removedSSRHelper(): never { - throw new Error( - `This method had to be removed due to a conceptual bug in RTK. - Please see https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details.` - ) - } - safeAssign(api.util, { - getRunningOperationPromises: removedSSRHelper as any, - getRunningOperationPromise: removedSSRHelper as any, + getRunningOperationPromises: getRunningOperationPromises as any, + getRunningOperationPromise: removalWarning as any, getRunningMutationThunk, getRunningMutationsThunk, getRunningQueryThunk, diff --git a/packages/toolkit/src/query/utils/isNotNullish.ts b/packages/toolkit/src/query/utils/isNotNullish.ts new file mode 100644 index 0000000000..e2d8f4b172 --- /dev/null +++ b/packages/toolkit/src/query/utils/isNotNullish.ts @@ -0,0 +1,3 @@ +export function isNotNullish(v: T | null | undefined): v is T { + return v != null +} diff --git a/packages/toolkit/src/tests/injectableCombineReducers.example.ts b/packages/toolkit/src/tests/injectableCombineReducers.example.ts new file mode 100644 index 0000000000..85b24359c5 --- /dev/null +++ b/packages/toolkit/src/tests/injectableCombineReducers.example.ts @@ -0,0 +1,63 @@ +/* eslint-disable import/first */ +// @ts-nocheck + +// reducer.ts or whatever + +import { combineSlices } from '@reduxjs/toolkit' + +import { sliceA } from 'fileA' +import { sliceB } from 'fileB' +import { lazySliceC } from 'fileC' +import type { lazySliceD } from 'fileD' + +import { anotherReducer } from 'somewhere' + +export interface LazyLoadedSlices {} + +export const rootReducer = combineSlices(sliceA, sliceB, { + another: anotherReducer, +}).withLazyLoadedSlices() +/* + results in a return type of + { + [sliceA.name]: SliceAState, + [sliceB.name]: SliceBState, + another: AnotherState, + [lazySliceC.name]?: SliceCState, // see fileC.ts to understand why this appears here + [lazySliceD.name]?: SliceDState, // see fileD.ts to understand why this appears here + } + */ + +// fileC.ts +// "naive" approach + +import { rootReducer, RootState } from './reducer' +import { createSlice } from '@reduxjs/toolkit' + +interface SliceCState { + foo: string +} + +declare module './reducer' { + export interface LazyLoadedSlices { + [lazySliceC.name]: SliceCState + } +} + +export const lazySliceC = createSlice({ + /* ... */ +}) +/** + * Synchronously call `injectSlice` in file. + */ +rootReducer.injectSlice(lazySliceC) + +// might want to add code for HMR as well here + +// this will still error - `lazySliceC` is optional here +const naiveSelectFoo = (state: RootState) => state.lazySliceC.foo + +const selectFoo = rootReducer.withSlice(lazySliceC).selector((state) => { + // `lazySlice` is guaranteed to not be `undefined` here. + return state.lazySlice.foo +})