From 2ad4bb4d8097da7705d9434d264cc12c6c3741d7 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 27 Nov 2024 21:29:33 +0000 Subject: [PATCH 1/6] Update to new version of upsert proposal --- packages/toolkit/src/combineSlices.ts | 10 +- packages/toolkit/src/createSlice.ts | 40 ++++---- .../toolkit/src/dynamicMiddleware/index.ts | 4 +- packages/toolkit/src/utils.ts | 93 +++++-------------- 4 files changed, 53 insertions(+), 94 deletions(-) diff --git a/packages/toolkit/src/combineSlices.ts b/packages/toolkit/src/combineSlices.ts index 26ff173e7d..a5353af937 100644 --- a/packages/toolkit/src/combineSlices.ts +++ b/packages/toolkit/src/combineSlices.ts @@ -8,7 +8,7 @@ import type { UnionToIntersection, WithOptionalProp, } from './tsHelpers' -import { emplace } from './utils' +import { getOrInsertComputed } from './utils' type SliceLike = { reducerPath: ReducerPath @@ -324,8 +324,10 @@ const createStateProxy = ( state: State, reducerMap: Partial>, ) => - emplace(stateProxyMap, state, { - insert: () => + getOrInsertComputed( + stateProxyMap, + state, + () => new Proxy(state, { get: (target, prop, receiver) => { if (prop === ORIGINAL_STATE) return target @@ -350,7 +352,7 @@ const createStateProxy = ( return result }, }), - }) as State + ) as State const original = (state: any) => { if (!isStateProxy(state)) { diff --git a/packages/toolkit/src/createSlice.ts b/packages/toolkit/src/createSlice.ts index e3d2c25c56..1d4f3e3712 100644 --- a/packages/toolkit/src/createSlice.ts +++ b/packages/toolkit/src/createSlice.ts @@ -26,7 +26,7 @@ import { createReducer } from './createReducer' import type { ActionReducerMapBuilder, TypedActionCreator } from './mapBuilders' import { executeReducerBuilderCallback } from './mapBuilders' import type { Id, TypeGuard } from './tsHelpers' -import { emplace } from './utils' +import { getOrInsertComputed } from './utils' const asyncThunkSymbol = /* @__PURE__ */ Symbol.for( 'rtk-slice-createasyncthunk', @@ -769,25 +769,25 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) { function getSelectors( selectState: (rootState: any) => State = selectSelf, ) { - const selectorCache = emplace(injectedSelectorCache, injected, { - insert: () => new WeakMap(), - }) - - return emplace(selectorCache, selectState, { - insert: () => { - const map: Record> = {} - for (const [name, selector] of Object.entries( - options.selectors ?? {}, - )) { - map[name] = wrapSelector( - selector, - selectState, - getInitialState, - injected, - ) - } - return map - }, + const selectorCache = getOrInsertComputed( + injectedSelectorCache, + injected, + () => new WeakMap(), + ) + + return getOrInsertComputed(selectorCache, selectState, () => { + const map: Record> = {} + for (const [name, selector] of Object.entries( + options.selectors ?? {}, + )) { + map[name] = wrapSelector( + selector, + selectState, + getInitialState, + injected, + ) + } + return map }) as any } return { diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index ed151b2979..7fc5a60892 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -3,7 +3,7 @@ import { compose } from 'redux' import { createAction } from '../createAction' import { isAllOf } from '../matchers' import { nanoid } from '../nanoid' -import { emplace, find } from '../utils' +import { find, getOrInsertComputed } from '../utils' import type { AddMiddleware, DynamicMiddleware, @@ -73,7 +73,7 @@ export const createDynamicMiddleware = < const getFinalMiddleware: Middleware<{}, State, DispatchType> = (api) => { const appliedMiddleware = Array.from(middlewareMap.values()).map((entry) => - emplace(entry.applied, api, { insert: () => entry.middleware(api) }), + getOrInsertComputed(entry.applied, api, entry.middleware), ) return compose(...appliedMiddleware) } diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 1f8445bb0f..157cc86b08 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -87,81 +87,38 @@ export function freezeDraftable(val: T) { return isDraftable(val) ? createNextState(val, () => {}) : val } -interface WeakMapEmplaceHandler { - /** - * Will be called to get value, if no value is currently in map. - */ - insert?(key: K, map: WeakMap): V - /** - * Will be called to update a value, if one exists already. - */ - update?(previous: V, key: K, map: WeakMap): V -} +export function getOrInsert( + map: WeakMap, + key: K, + value: V, +): V +export function getOrInsert(map: Map, key: K, value: V): V +export function getOrInsert( + map: Map | WeakMap, + key: K, + value: V, +): V { + if (map.has(key)) return map.get(key) as V -interface MapEmplaceHandler { - /** - * Will be called to get value, if no value is currently in map. - */ - insert?(key: K, map: Map): V - /** - * Will be called to update a value, if one exists already. - */ - update?(previous: V, key: K, map: Map): V + return map.set(key, value).get(key) as V } -export function emplace( - map: Map, +export function getOrInsertComputed( + map: WeakMap, key: K, - handler: MapEmplaceHandler, + compute: (key: K) => V, ): V -export function emplace( - map: WeakMap, +export function getOrInsertComputed( + map: Map, key: K, - handler: WeakMapEmplaceHandler, + compute: (key: K) => V, ): V -/** - * Allow inserting a new value, or updating an existing one - * @throws if called for a key with no current value and no `insert` handler is provided - * @returns current value in map (after insertion/updating) - * ```ts - * // return current value if already in map, otherwise initialise to 0 and return that - * const num = emplace(map, key, { - * insert: () => 0 - * }) - * - * // increase current value by one if already in map, otherwise initialise to 0 - * const num = emplace(map, key, { - * update: (n) => n + 1, - * insert: () => 0, - * }) - * - * // only update if value's already in the map - and increase it by one - * if (map.has(key)) { - * const num = emplace(map, key, { - * update: (n) => n + 1, - * }) - * } - * ``` - * - * @remarks - * Based on https://github.com/tc39/proposal-upsert currently in Stage 2 - maybe in a few years we'll be able to replace this with direct method calls - */ -export function emplace( - map: WeakMap, +export function getOrInsertComputed( + map: Map | WeakMap, key: K, - handler: WeakMapEmplaceHandler, + compute: (key: K) => V, ): V { - if (map.has(key)) { - let value = map.get(key) as V - if (handler.update) { - value = handler.update(value, key, map) - map.set(key, value) - } - return value - } - if (!handler.insert) - throw new Error('No insert provided for key not already in map') - const inserted = handler.insert(key, map) - map.set(key, inserted) - return inserted + if (map.has(key)) return map.get(key) as V + + return map.set(key, compute(key)).get(key) as V } From 3bb84862ec295ca420c649dae3924f83549a1123 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 27 Nov 2024 23:12:44 +0000 Subject: [PATCH 2/6] Simplify listener/dynamic middleware code --- .../toolkit/src/dynamicMiddleware/index.ts | 17 ++++----- .../toolkit/src/dynamicMiddleware/types.ts | 1 - .../toolkit/src/listenerMiddleware/index.ts | 27 +++++--------- .../toolkit/src/listenerMiddleware/types.ts | 35 ++++++++++++++----- packages/toolkit/src/utils.ts | 13 ------- 5 files changed, 42 insertions(+), 51 deletions(-) diff --git a/packages/toolkit/src/dynamicMiddleware/index.ts b/packages/toolkit/src/dynamicMiddleware/index.ts index 7fc5a60892..8e61d6769b 100644 --- a/packages/toolkit/src/dynamicMiddleware/index.ts +++ b/packages/toolkit/src/dynamicMiddleware/index.ts @@ -3,7 +3,7 @@ import { compose } from 'redux' import { createAction } from '../createAction' import { isAllOf } from '../matchers' import { nanoid } from '../nanoid' -import { find, getOrInsertComputed } from '../utils' +import { getOrInsertComputed } from '../utils' import type { AddMiddleware, DynamicMiddleware, @@ -23,7 +23,6 @@ const createMiddlewareEntry = < >( middleware: Middleware, ): MiddlewareEntry => ({ - id: nanoid(), middleware, applied: new Map(), }) @@ -38,7 +37,10 @@ export const createDynamicMiddleware = < DispatchType extends Dispatch = Dispatch, >(): DynamicMiddlewareInstance => { const instanceId = nanoid() - const middlewareMap = new Map>() + const middlewareMap = new Map< + Middleware, + MiddlewareEntry + >() const withMiddleware = Object.assign( createAction( @@ -58,14 +60,7 @@ export const createDynamicMiddleware = < ...middlewares: Middleware[] ) { middlewares.forEach((middleware) => { - let entry = find( - Array.from(middlewareMap.values()), - (entry) => entry.middleware === middleware, - ) - if (!entry) { - entry = createMiddlewareEntry(middleware) - } - middlewareMap.set(entry.id, entry) + getOrInsertComputed(middlewareMap, middleware, createMiddlewareEntry) }) }, { withTypes: () => addMiddleware }, diff --git a/packages/toolkit/src/dynamicMiddleware/types.ts b/packages/toolkit/src/dynamicMiddleware/types.ts index ee8c37a21b..989c7ffcc0 100644 --- a/packages/toolkit/src/dynamicMiddleware/types.ts +++ b/packages/toolkit/src/dynamicMiddleware/types.ts @@ -59,7 +59,6 @@ export type MiddlewareEntry< State = unknown, DispatchType extends Dispatch = Dispatch, > = { - id: string middleware: Middleware applied: Map< MiddlewareAPI, diff --git a/packages/toolkit/src/listenerMiddleware/index.ts b/packages/toolkit/src/listenerMiddleware/index.ts index efa2912ad3..9cb2e87dda 100644 --- a/packages/toolkit/src/listenerMiddleware/index.ts +++ b/packages/toolkit/src/listenerMiddleware/index.ts @@ -2,9 +2,7 @@ import type { Action, Dispatch, MiddlewareAPI, UnknownAction } from 'redux' import { isAction } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import { createAction } from '../createAction' -import { nanoid } from '../nanoid' -import { find } from '../utils' import { TaskAbortError, listenerCancelled, @@ -48,6 +46,7 @@ import { catchRejection, noop, } from './utils' +import { getOrInsertComputed } from '@internal/utils' export { TaskAbortError } from './exceptions' export type { AsyncTaskExecutor, @@ -221,9 +220,7 @@ export const createListenerEntry: TypedCreateListenerEntry = (options: FallbackAddListenerOptions) => { const { type, predicate, effect } = getListenerEntryPropsFrom(options) - const id = nanoid() const entry: ListenerEntry = { - id, effect, type, predicate, @@ -247,7 +244,7 @@ const cancelActiveListeners = ( } const createClearListenerMiddleware = ( - listenerMap: Map, + listenerMap: Map, ) => { return () => { listenerMap.forEach(cancelActiveListeners) @@ -324,15 +321,15 @@ export const createListenerMiddleware = < >( middlewareOptions: CreateListenerMiddlewareOptions = {}, ) => { - const listenerMap = new Map() + const listenerMap = new Map() const { extra, onError = defaultErrorHandler } = middlewareOptions assertFunction(onError, 'onError') const insertEntry = (entry: ListenerEntry) => { - entry.unsubscribe = () => listenerMap.delete(entry!.id) + entry.unsubscribe = () => listenerMap.delete(entry.effect) - listenerMap.set(entry.id, entry) + listenerMap.set(entry.effect, entry) return (cancelOptions?: UnsubscribeListenerOptions) => { entry.unsubscribe() if (cancelOptions?.cancelActive) { @@ -342,15 +339,9 @@ export const createListenerMiddleware = < } const startListening = ((options: FallbackAddListenerOptions) => { - let entry = find( - Array.from(listenerMap.values()), - (existingEntry) => existingEntry.effect === options.effect, + const entry = getOrInsertComputed(listenerMap, options.effect, () => + createListenerEntry(options as any), ) - - if (!entry) { - entry = createListenerEntry(options as any) - } - return insertEntry(entry) }) as AddListenerOverloads @@ -363,7 +354,7 @@ export const createListenerMiddleware = < ): boolean => { const { type, effect, predicate } = getListenerEntryPropsFrom(options) - const entry = find(Array.from(listenerMap.values()), (entry) => { + const entry = Array.from(listenerMap.values()).find((entry) => { const matchPredicateOrType = typeof type === 'string' ? entry.type === type @@ -419,7 +410,7 @@ export const createListenerMiddleware = < fork: createFork(internalTaskController.signal, autoJoinPromises), unsubscribe: entry.unsubscribe, subscribe: () => { - listenerMap.set(entry.id, entry) + listenerMap.set(entry.effect, entry) }, cancelActiveListeners: () => { entry.pending.forEach((controller, _, set) => { diff --git a/packages/toolkit/src/listenerMiddleware/types.ts b/packages/toolkit/src/listenerMiddleware/types.ts index b5980e1085..8dceea993f 100644 --- a/packages/toolkit/src/listenerMiddleware/types.ts +++ b/packages/toolkit/src/listenerMiddleware/types.ts @@ -578,9 +578,13 @@ export type TypedAddListener< OverrideStateType, unknown, UnknownAction - >, - OverrideExtraArgument = unknown, - >() => TypedAddListener + >, + OverrideExtraArgument = unknown, + >() => TypedAddListener< + OverrideStateType, + OverrideDispatchType, + OverrideExtraArgument + > } /** @@ -641,7 +645,11 @@ export type TypedRemoveListener< UnknownAction >, OverrideExtraArgument = unknown, - >() => TypedRemoveListener + >() => TypedRemoveListener< + OverrideStateType, + OverrideDispatchType, + OverrideExtraArgument + > } /** @@ -701,7 +709,11 @@ export type TypedStartListening< UnknownAction >, OverrideExtraArgument = unknown, - >() => TypedStartListening + >() => TypedStartListening< + OverrideStateType, + OverrideDispatchType, + OverrideExtraArgument + > } /** @@ -756,7 +768,11 @@ export type TypedStopListening< UnknownAction >, OverrideExtraArgument = unknown, - >() => TypedStopListening + >() => TypedStopListening< + OverrideStateType, + OverrideDispatchType, + OverrideExtraArgument + > } /** @@ -813,7 +829,11 @@ export type TypedCreateListenerEntry< UnknownAction >, OverrideExtraArgument = unknown, - >() => TypedStopListening + >() => TypedStopListening< + OverrideStateType, + OverrideDispatchType, + OverrideExtraArgument + > } /** @@ -825,7 +845,6 @@ export type ListenerEntry< State = unknown, DispatchType extends Dispatch = Dispatch, > = { - id: string effect: ListenerEffect unsubscribe: () => void pending: Set diff --git a/packages/toolkit/src/utils.ts b/packages/toolkit/src/utils.ts index 157cc86b08..6607f4b339 100644 --- a/packages/toolkit/src/utils.ts +++ b/packages/toolkit/src/utils.ts @@ -26,19 +26,6 @@ export function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } -export function find( - iterable: Iterable, - comparator: (item: T) => boolean, -): T | undefined { - for (const entry of iterable) { - if (comparator(entry)) { - return entry - } - } - - return undefined -} - export class Tuple = []> extends Array< Items[number] > { From d632dd591f78969dd11c84f0871a28e2f274b408 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 27 Nov 2024 23:34:20 +0000 Subject: [PATCH 3/6] use getOrInsert in RTKQ --- packages/toolkit/src/query/core/buildInitiate.ts | 5 ++--- packages/toolkit/src/query/utils/getOrInsert.ts | 15 +++++++++++++++ packages/toolkit/src/query/utils/index.ts | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 packages/toolkit/src/query/utils/getOrInsert.ts diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index ff2ab45456..ca2d6854e0 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -16,7 +16,7 @@ import type { QueryDefinition, ResultTypeFrom, } from '../endpointDefinitions' -import { countObjectKeys, isNotNullish } from '../utils' +import { countObjectKeys, getOrInsert, isNotNullish } from '../utils' import type { SubscriptionOptions } from './apiState' import type { QueryResultSelectorResult } from './buildSelectors' import type { MutationThunk, QueryThunk, QueryThunkArg } from './buildThunks' @@ -391,9 +391,8 @@ You must add the middleware for RTK-Query to function correctly!`, ) if (!runningQuery && !skippedSynchronously && !forceQueryFn) { - const running = runningQueries.get(dispatch) || {} + const running = getOrInsert(runningQueries, dispatch, {}) running[queryCacheKey] = statePromise - runningQueries.set(dispatch, running) statePromise.then(() => { delete running[queryCacheKey] diff --git a/packages/toolkit/src/query/utils/getOrInsert.ts b/packages/toolkit/src/query/utils/getOrInsert.ts new file mode 100644 index 0000000000..124da032ea --- /dev/null +++ b/packages/toolkit/src/query/utils/getOrInsert.ts @@ -0,0 +1,15 @@ +export function getOrInsert( + map: WeakMap, + key: K, + value: V, +): V +export function getOrInsert(map: Map, key: K, value: V): V +export function getOrInsert( + map: Map | WeakMap, + key: K, + value: V, +): V { + if (map.has(key)) return map.get(key) as V + + return map.set(key, value).get(key) as V +} diff --git a/packages/toolkit/src/query/utils/index.ts b/packages/toolkit/src/query/utils/index.ts index 0eb7c62ce9..916b32fd60 100644 --- a/packages/toolkit/src/query/utils/index.ts +++ b/packages/toolkit/src/query/utils/index.ts @@ -8,3 +8,4 @@ export * from './isNotNullish' export * from './isOnline' export * from './isValidUrl' export * from './joinUrls' +export * from './getOrInsert' From 98d775363303b0f0469a6992ca807615cc77279a Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Wed, 27 Nov 2024 23:35:49 +0000 Subject: [PATCH 4/6] fix wrong import --- packages/toolkit/src/listenerMiddleware/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/listenerMiddleware/index.ts b/packages/toolkit/src/listenerMiddleware/index.ts index 9cb2e87dda..c5a623a220 100644 --- a/packages/toolkit/src/listenerMiddleware/index.ts +++ b/packages/toolkit/src/listenerMiddleware/index.ts @@ -2,6 +2,7 @@ import type { Action, Dispatch, MiddlewareAPI, UnknownAction } from 'redux' import { isAction } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import { createAction } from '../createAction' +import { getOrInsertComputed } from '../utils' import { TaskAbortError, @@ -46,7 +47,6 @@ import { catchRejection, noop, } from './utils' -import { getOrInsertComputed } from '@internal/utils' export { TaskAbortError } from './exceptions' export type { AsyncTaskExecutor, From c65558abea02e29fa56af9d988990c39678cd974 Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 28 Nov 2024 18:42:28 +0000 Subject: [PATCH 5/6] Fix listener logic to properly compare combination of effect and predicate/type --- .../toolkit/src/listenerMiddleware/index.ts | 47 +++++++++++-------- .../toolkit/src/listenerMiddleware/types.ts | 1 + 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/toolkit/src/listenerMiddleware/index.ts b/packages/toolkit/src/listenerMiddleware/index.ts index c5a623a220..cfefa17e09 100644 --- a/packages/toolkit/src/listenerMiddleware/index.ts +++ b/packages/toolkit/src/listenerMiddleware/index.ts @@ -2,7 +2,7 @@ import type { Action, Dispatch, MiddlewareAPI, UnknownAction } from 'redux' import { isAction } from 'redux' import type { ThunkDispatch } from 'redux-thunk' import { createAction } from '../createAction' -import { getOrInsertComputed } from '../utils' +import { nanoid } from '../nanoid' import { TaskAbortError, @@ -221,6 +221,7 @@ export const createListenerEntry: TypedCreateListenerEntry = const { type, predicate, effect } = getListenerEntryPropsFrom(options) const entry: ListenerEntry = { + id: nanoid(), effect, type, predicate, @@ -235,6 +236,22 @@ export const createListenerEntry: TypedCreateListenerEntry = { withTypes: () => createListenerEntry }, ) as unknown as TypedCreateListenerEntry +const findListenerEntry = ( + listenerMap: Map, + options: FallbackAddListenerOptions, +) => { + const { type, effect, predicate } = getListenerEntryPropsFrom(options) + + return Array.from(listenerMap.values()).find((entry) => { + const matchPredicateOrType = + typeof type === 'string' + ? entry.type === type + : entry.predicate === predicate + + return matchPredicateOrType && entry.effect === effect + }) +} + const cancelActiveListeners = ( entry: ListenerEntry>, ) => { @@ -244,7 +261,7 @@ const cancelActiveListeners = ( } const createClearListenerMiddleware = ( - listenerMap: Map, + listenerMap: Map, ) => { return () => { listenerMap.forEach(cancelActiveListeners) @@ -321,15 +338,15 @@ export const createListenerMiddleware = < >( middlewareOptions: CreateListenerMiddlewareOptions = {}, ) => { - const listenerMap = new Map() + const listenerMap = new Map() const { extra, onError = defaultErrorHandler } = middlewareOptions assertFunction(onError, 'onError') const insertEntry = (entry: ListenerEntry) => { - entry.unsubscribe = () => listenerMap.delete(entry.effect) + entry.unsubscribe = () => listenerMap.delete(entry.id) - listenerMap.set(entry.effect, entry) + listenerMap.set(entry.id, entry) return (cancelOptions?: UnsubscribeListenerOptions) => { entry.unsubscribe() if (cancelOptions?.cancelActive) { @@ -339,9 +356,10 @@ export const createListenerMiddleware = < } const startListening = ((options: FallbackAddListenerOptions) => { - const entry = getOrInsertComputed(listenerMap, options.effect, () => - createListenerEntry(options as any), - ) + const entry = + findListenerEntry(listenerMap, options) ?? + createListenerEntry(options as any) + return insertEntry(entry) }) as AddListenerOverloads @@ -352,16 +370,7 @@ export const createListenerMiddleware = < const stopListening = ( options: FallbackAddListenerOptions & UnsubscribeListenerOptions, ): boolean => { - const { type, effect, predicate } = getListenerEntryPropsFrom(options) - - const entry = Array.from(listenerMap.values()).find((entry) => { - const matchPredicateOrType = - typeof type === 'string' - ? entry.type === type - : entry.predicate === predicate - - return matchPredicateOrType && entry.effect === effect - }) + const entry = findListenerEntry(listenerMap, options) if (entry) { entry.unsubscribe() @@ -410,7 +419,7 @@ export const createListenerMiddleware = < fork: createFork(internalTaskController.signal, autoJoinPromises), unsubscribe: entry.unsubscribe, subscribe: () => { - listenerMap.set(entry.effect, entry) + listenerMap.set(entry.id, entry) }, cancelActiveListeners: () => { entry.pending.forEach((controller, _, set) => { diff --git a/packages/toolkit/src/listenerMiddleware/types.ts b/packages/toolkit/src/listenerMiddleware/types.ts index 8dceea993f..7e6f6c2783 100644 --- a/packages/toolkit/src/listenerMiddleware/types.ts +++ b/packages/toolkit/src/listenerMiddleware/types.ts @@ -845,6 +845,7 @@ export type ListenerEntry< State = unknown, DispatchType extends Dispatch = Dispatch, > = { + id: string effect: ListenerEffect unsubscribe: () => void pending: Set From 05a8322da52292178ad36360e3d853cf1d21530f Mon Sep 17 00:00:00 2001 From: Ben Durrant Date: Thu, 28 Nov 2024 18:48:19 +0000 Subject: [PATCH 6/6] add test for fix --- .../tests/listenerMiddleware.test.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts index 56939af639..ad657508e2 100644 --- a/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts +++ b/packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts @@ -117,6 +117,7 @@ describe('createListenerMiddleware', () => { const testAction1 = createAction('testAction1') type TestAction1 = ReturnType const testAction2 = createAction('testAction2') + type TestAction2 = ReturnType const testAction3 = createAction('testAction3') beforeAll(() => { @@ -339,6 +340,27 @@ describe('createListenerMiddleware', () => { ]) }) + test('subscribing with the same effect but different predicate is allowed', () => { + const effect = vi.fn((_: TestAction1 | TestAction2) => {}) + + startListening({ + actionCreator: testAction1, + effect, + }) + startListening({ + actionCreator: testAction2, + effect, + }) + + store.dispatch(testAction1('a')) + store.dispatch(testAction2('b')) + + expect(effect.mock.calls).toEqual([ + [testAction1('a'), middlewareApi], + [testAction2('b'), middlewareApi], + ]) + }) + test('unsubscribing via callback', () => { const effect = vi.fn((_: TestAction1) => {})