Skip to content

Commit fdfc3b7

Browse files
authored
Merge pull request #4735 from reduxjs/upsert-new
Update to new version of upsert proposal, and fix listener equality checks
2 parents ee22bef + 05a8322 commit fdfc3b7

File tree

11 files changed

+147
-150
lines changed

11 files changed

+147
-150
lines changed

packages/toolkit/src/combineSlices.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
UnionToIntersection,
99
WithOptionalProp,
1010
} from './tsHelpers'
11-
import { emplace } from './utils'
11+
import { getOrInsertComputed } from './utils'
1212

1313
type SliceLike<ReducerPath extends string, State> = {
1414
reducerPath: ReducerPath
@@ -324,8 +324,10 @@ const createStateProxy = <State extends object>(
324324
state: State,
325325
reducerMap: Partial<Record<string, Reducer>>,
326326
) =>
327-
emplace(stateProxyMap, state, {
328-
insert: () =>
327+
getOrInsertComputed(
328+
stateProxyMap,
329+
state,
330+
() =>
329331
new Proxy(state, {
330332
get: (target, prop, receiver) => {
331333
if (prop === ORIGINAL_STATE) return target
@@ -350,7 +352,7 @@ const createStateProxy = <State extends object>(
350352
return result
351353
},
352354
}),
353-
}) as State
355+
) as State
354356

355357
const original = (state: any) => {
356358
if (!isStateProxy(state)) {

packages/toolkit/src/createSlice.ts

+20-20
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { createReducer } from './createReducer'
2626
import type { ActionReducerMapBuilder, TypedActionCreator } from './mapBuilders'
2727
import { executeReducerBuilderCallback } from './mapBuilders'
2828
import type { Id, TypeGuard } from './tsHelpers'
29-
import { emplace } from './utils'
29+
import { getOrInsertComputed } from './utils'
3030

3131
const asyncThunkSymbol = /* @__PURE__ */ Symbol.for(
3232
'rtk-slice-createasyncthunk',
@@ -769,25 +769,25 @@ export function buildCreateSlice({ creators }: BuildCreateSliceConfig = {}) {
769769
function getSelectors(
770770
selectState: (rootState: any) => State = selectSelf,
771771
) {
772-
const selectorCache = emplace(injectedSelectorCache, injected, {
773-
insert: () => new WeakMap(),
774-
})
775-
776-
return emplace(selectorCache, selectState, {
777-
insert: () => {
778-
const map: Record<string, Selector<any, any>> = {}
779-
for (const [name, selector] of Object.entries(
780-
options.selectors ?? {},
781-
)) {
782-
map[name] = wrapSelector(
783-
selector,
784-
selectState,
785-
getInitialState,
786-
injected,
787-
)
788-
}
789-
return map
790-
},
772+
const selectorCache = getOrInsertComputed(
773+
injectedSelectorCache,
774+
injected,
775+
() => new WeakMap(),
776+
)
777+
778+
return getOrInsertComputed(selectorCache, selectState, () => {
779+
const map: Record<string, Selector<any, any>> = {}
780+
for (const [name, selector] of Object.entries(
781+
options.selectors ?? {},
782+
)) {
783+
map[name] = wrapSelector(
784+
selector,
785+
selectState,
786+
getInitialState,
787+
injected,
788+
)
789+
}
790+
return map
791791
}) as any
792792
}
793793
return {

packages/toolkit/src/dynamicMiddleware/index.ts

+7-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { compose } from 'redux'
33
import { createAction } from '../createAction'
44
import { isAllOf } from '../matchers'
55
import { nanoid } from '../nanoid'
6-
import { emplace, find } from '../utils'
6+
import { getOrInsertComputed } from '../utils'
77
import type {
88
AddMiddleware,
99
DynamicMiddleware,
@@ -23,7 +23,6 @@ const createMiddlewareEntry = <
2323
>(
2424
middleware: Middleware<any, State, DispatchType>,
2525
): MiddlewareEntry<State, DispatchType> => ({
26-
id: nanoid(),
2726
middleware,
2827
applied: new Map(),
2928
})
@@ -38,7 +37,10 @@ export const createDynamicMiddleware = <
3837
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
3938
>(): DynamicMiddlewareInstance<State, DispatchType> => {
4039
const instanceId = nanoid()
41-
const middlewareMap = new Map<string, MiddlewareEntry<State, DispatchType>>()
40+
const middlewareMap = new Map<
41+
Middleware<any, State, DispatchType>,
42+
MiddlewareEntry<State, DispatchType>
43+
>()
4244

4345
const withMiddleware = Object.assign(
4446
createAction(
@@ -58,22 +60,15 @@ export const createDynamicMiddleware = <
5860
...middlewares: Middleware<any, State, DispatchType>[]
5961
) {
6062
middlewares.forEach((middleware) => {
61-
let entry = find(
62-
Array.from(middlewareMap.values()),
63-
(entry) => entry.middleware === middleware,
64-
)
65-
if (!entry) {
66-
entry = createMiddlewareEntry(middleware)
67-
}
68-
middlewareMap.set(entry.id, entry)
63+
getOrInsertComputed(middlewareMap, middleware, createMiddlewareEntry)
6964
})
7065
},
7166
{ withTypes: () => addMiddleware },
7267
) as AddMiddleware<State, DispatchType>
7368

7469
const getFinalMiddleware: Middleware<{}, State, DispatchType> = (api) => {
7570
const appliedMiddleware = Array.from(middlewareMap.values()).map((entry) =>
76-
emplace(entry.applied, api, { insert: () => entry.middleware(api) }),
71+
getOrInsertComputed(entry.applied, api, entry.middleware),
7772
)
7873
return compose(...appliedMiddleware)
7974
}

packages/toolkit/src/dynamicMiddleware/types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ export type MiddlewareEntry<
5959
State = unknown,
6060
DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>,
6161
> = {
62-
id: string
6362
middleware: Middleware<any, State, DispatchType>
6463
applied: Map<
6564
MiddlewareAPI<DispatchType, State>,

packages/toolkit/src/listenerMiddleware/index.ts

+22-22
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { ThunkDispatch } from 'redux-thunk'
44
import { createAction } from '../createAction'
55
import { nanoid } from '../nanoid'
66

7-
import { find } from '../utils'
87
import {
98
TaskAbortError,
109
listenerCancelled,
@@ -221,9 +220,8 @@ export const createListenerEntry: TypedCreateListenerEntry<unknown> =
221220
(options: FallbackAddListenerOptions) => {
222221
const { type, predicate, effect } = getListenerEntryPropsFrom(options)
223222

224-
const id = nanoid()
225223
const entry: ListenerEntry<unknown> = {
226-
id,
224+
id: nanoid(),
227225
effect,
228226
type,
229227
predicate,
@@ -238,6 +236,22 @@ export const createListenerEntry: TypedCreateListenerEntry<unknown> =
238236
{ withTypes: () => createListenerEntry },
239237
) as unknown as TypedCreateListenerEntry<unknown>
240238

239+
const findListenerEntry = (
240+
listenerMap: Map<string, ListenerEntry>,
241+
options: FallbackAddListenerOptions,
242+
) => {
243+
const { type, effect, predicate } = getListenerEntryPropsFrom(options)
244+
245+
return Array.from(listenerMap.values()).find((entry) => {
246+
const matchPredicateOrType =
247+
typeof type === 'string'
248+
? entry.type === type
249+
: entry.predicate === predicate
250+
251+
return matchPredicateOrType && entry.effect === effect
252+
})
253+
}
254+
241255
const cancelActiveListeners = (
242256
entry: ListenerEntry<unknown, Dispatch<UnknownAction>>,
243257
) => {
@@ -330,7 +344,7 @@ export const createListenerMiddleware = <
330344
assertFunction(onError, 'onError')
331345

332346
const insertEntry = (entry: ListenerEntry) => {
333-
entry.unsubscribe = () => listenerMap.delete(entry!.id)
347+
entry.unsubscribe = () => listenerMap.delete(entry.id)
334348

335349
listenerMap.set(entry.id, entry)
336350
return (cancelOptions?: UnsubscribeListenerOptions) => {
@@ -342,14 +356,9 @@ export const createListenerMiddleware = <
342356
}
343357

344358
const startListening = ((options: FallbackAddListenerOptions) => {
345-
let entry = find(
346-
Array.from(listenerMap.values()),
347-
(existingEntry) => existingEntry.effect === options.effect,
348-
)
349-
350-
if (!entry) {
351-
entry = createListenerEntry(options as any)
352-
}
359+
const entry =
360+
findListenerEntry(listenerMap, options) ??
361+
createListenerEntry(options as any)
353362

354363
return insertEntry(entry)
355364
}) as AddListenerOverloads<any>
@@ -361,16 +370,7 @@ export const createListenerMiddleware = <
361370
const stopListening = (
362371
options: FallbackAddListenerOptions & UnsubscribeListenerOptions,
363372
): boolean => {
364-
const { type, effect, predicate } = getListenerEntryPropsFrom(options)
365-
366-
const entry = find(Array.from(listenerMap.values()), (entry) => {
367-
const matchPredicateOrType =
368-
typeof type === 'string'
369-
? entry.type === type
370-
: entry.predicate === predicate
371-
372-
return matchPredicateOrType && entry.effect === effect
373-
})
373+
const entry = findListenerEntry(listenerMap, options)
374374

375375
if (entry) {
376376
entry.unsubscribe()

packages/toolkit/src/listenerMiddleware/tests/listenerMiddleware.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ describe('createListenerMiddleware', () => {
117117
const testAction1 = createAction<string>('testAction1')
118118
type TestAction1 = ReturnType<typeof testAction1>
119119
const testAction2 = createAction<string>('testAction2')
120+
type TestAction2 = ReturnType<typeof testAction2>
120121
const testAction3 = createAction<string>('testAction3')
121122

122123
beforeAll(() => {
@@ -339,6 +340,27 @@ describe('createListenerMiddleware', () => {
339340
])
340341
})
341342

343+
test('subscribing with the same effect but different predicate is allowed', () => {
344+
const effect = vi.fn((_: TestAction1 | TestAction2) => {})
345+
346+
startListening({
347+
actionCreator: testAction1,
348+
effect,
349+
})
350+
startListening({
351+
actionCreator: testAction2,
352+
effect,
353+
})
354+
355+
store.dispatch(testAction1('a'))
356+
store.dispatch(testAction2('b'))
357+
358+
expect(effect.mock.calls).toEqual([
359+
[testAction1('a'), middlewareApi],
360+
[testAction2('b'), middlewareApi],
361+
])
362+
})
363+
342364
test('unsubscribing via callback', () => {
343365
const effect = vi.fn((_: TestAction1) => {})
344366

packages/toolkit/src/listenerMiddleware/types.ts

+27-7
Original file line numberDiff line numberDiff line change
@@ -578,9 +578,13 @@ export type TypedAddListener<
578578
OverrideStateType,
579579
unknown,
580580
UnknownAction
581-
>,
582-
OverrideExtraArgument = unknown,
583-
>() => TypedAddListener<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
581+
>,
582+
OverrideExtraArgument = unknown,
583+
>() => TypedAddListener<
584+
OverrideStateType,
585+
OverrideDispatchType,
586+
OverrideExtraArgument
587+
>
584588
}
585589

586590
/**
@@ -641,7 +645,11 @@ export type TypedRemoveListener<
641645
UnknownAction
642646
>,
643647
OverrideExtraArgument = unknown,
644-
>() => TypedRemoveListener<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
648+
>() => TypedRemoveListener<
649+
OverrideStateType,
650+
OverrideDispatchType,
651+
OverrideExtraArgument
652+
>
645653
}
646654

647655
/**
@@ -701,7 +709,11 @@ export type TypedStartListening<
701709
UnknownAction
702710
>,
703711
OverrideExtraArgument = unknown,
704-
>() => TypedStartListening<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
712+
>() => TypedStartListening<
713+
OverrideStateType,
714+
OverrideDispatchType,
715+
OverrideExtraArgument
716+
>
705717
}
706718

707719
/**
@@ -756,7 +768,11 @@ export type TypedStopListening<
756768
UnknownAction
757769
>,
758770
OverrideExtraArgument = unknown,
759-
>() => TypedStopListening<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
771+
>() => TypedStopListening<
772+
OverrideStateType,
773+
OverrideDispatchType,
774+
OverrideExtraArgument
775+
>
760776
}
761777

762778
/**
@@ -813,7 +829,11 @@ export type TypedCreateListenerEntry<
813829
UnknownAction
814830
>,
815831
OverrideExtraArgument = unknown,
816-
>() => TypedStopListening<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
832+
>() => TypedStopListening<
833+
OverrideStateType,
834+
OverrideDispatchType,
835+
OverrideExtraArgument
836+
>
817837
}
818838

819839
/**

packages/toolkit/src/query/core/buildInitiate.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {
1616
QueryDefinition,
1717
ResultTypeFrom,
1818
} from '../endpointDefinitions'
19-
import { countObjectKeys, isNotNullish } from '../utils'
19+
import { countObjectKeys, getOrInsert, isNotNullish } from '../utils'
2020
import type { SubscriptionOptions } from './apiState'
2121
import type { QueryResultSelectorResult } from './buildSelectors'
2222
import type { MutationThunk, QueryThunk, QueryThunkArg } from './buildThunks'
@@ -391,9 +391,8 @@ You must add the middleware for RTK-Query to function correctly!`,
391391
)
392392

393393
if (!runningQuery && !skippedSynchronously && !forceQueryFn) {
394-
const running = runningQueries.get(dispatch) || {}
394+
const running = getOrInsert(runningQueries, dispatch, {})
395395
running[queryCacheKey] = statePromise
396-
runningQueries.set(dispatch, running)
397396

398397
statePromise.then(() => {
399398
delete running[queryCacheKey]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function getOrInsert<K extends object, V>(
2+
map: WeakMap<K, V>,
3+
key: K,
4+
value: V,
5+
): V
6+
export function getOrInsert<K, V>(map: Map<K, V>, key: K, value: V): V
7+
export function getOrInsert<K extends object, V>(
8+
map: Map<K, V> | WeakMap<K, V>,
9+
key: K,
10+
value: V,
11+
): V {
12+
if (map.has(key)) return map.get(key) as V
13+
14+
return map.set(key, value).get(key) as V
15+
}

packages/toolkit/src/query/utils/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './isNotNullish'
88
export * from './isOnline'
99
export * from './isValidUrl'
1010
export * from './joinUrls'
11+
export * from './getOrInsert'

0 commit comments

Comments
 (0)