@@ -59,25 +59,6 @@ function assertFunction(
59
59
}
60
60
}
61
61
62
- export const hasMatchFunction = < T > (
63
- v : Matcher < T >
64
- ) : v is HasMatchFunction < T > => {
65
- return v && typeof ( v as HasMatchFunction < T > ) . match === 'function'
66
- }
67
-
68
- export const isActionCreator = (
69
- item : Function
70
- ) : item is TypedActionCreator < any > => {
71
- return (
72
- typeof item === 'function' &&
73
- typeof ( item as any ) . type === 'string' &&
74
- hasMatchFunction ( item as any )
75
- )
76
- }
77
-
78
- /** @public */
79
- export type Matcher < T > = HasMatchFunction < T > | MatchFunction < T >
80
-
81
62
type Unsubscribe = ( ) => void
82
63
83
64
type GuardedType < T > = T extends ( x : any , ...args : unknown [ ] ) => x is infer T
@@ -110,6 +91,7 @@ export interface ActionListenerMiddlewareAPI<S, D extends Dispatch<AnyAction>>
110
91
extends MiddlewareAPI < D , S > {
111
92
getOriginalState : ( ) => S
112
93
unsubscribe ( ) : void
94
+ subscribe ( ) : void
113
95
condition : ConditionFunction < S >
114
96
currentPhase : MiddlewarePhase
115
97
// TODO Figure out how to pass this through the other types correctly
@@ -146,11 +128,15 @@ export interface CreateListenerMiddlewareOptions<ExtraArgument = unknown> {
146
128
onError ?: ListenerErrorHandler
147
129
}
148
130
131
+ /**
132
+ * The possible overloads and options for defining a listener. The return type of each function is specified as a generic arg, so the overloads can be reused for multiple different functions
133
+ */
149
134
interface AddListenerOverloads <
150
135
Return ,
151
136
S = unknown ,
152
137
D extends Dispatch = ThunkDispatch < S , unknown , AnyAction >
153
138
> {
139
+ /** Accepts a "listener predicate" that is also a TS type predicate for the action*/
154
140
< MA extends AnyAction , LP extends ListenerPredicate < MA , S > > (
155
141
options : {
156
142
actionCreator ?: never
@@ -160,6 +146,8 @@ interface AddListenerOverloads<
160
146
listener : ActionListener < ListenerPredicateGuardedActionType < LP > , S , D >
161
147
} & ActionListenerOptions
162
148
) : Return
149
+
150
+ /** Accepts an RTK action creator, like `incrementByAmount` */
163
151
< C extends TypedActionCreator < any > > (
164
152
options : {
165
153
actionCreator : C
@@ -169,6 +157,8 @@ interface AddListenerOverloads<
169
157
listener : ActionListener < ReturnType < C > , S , D >
170
158
} & ActionListenerOptions
171
159
) : Return
160
+
161
+ /** Accepts a specific action type string */
172
162
< T extends string > (
173
163
options : {
174
164
actionCreator ?: never
@@ -178,6 +168,8 @@ interface AddListenerOverloads<
178
168
listener : ActionListener < Action < T > , S , D >
179
169
} & ActionListenerOptions
180
170
) : Return
171
+
172
+ /** Accepts an RTK matcher function, such as `incrementByAmount.match` */
181
173
< MA extends AnyAction , M extends MatchFunction < MA > > (
182
174
options : {
183
175
actionCreator ?: never
@@ -188,6 +180,7 @@ interface AddListenerOverloads<
188
180
} & ActionListenerOptions
189
181
) : Return
190
182
183
+ /** Accepts a "listener predicate" that just returns a boolean, no type assertion */
191
184
< LP extends AnyActionListenerPredicate < S > > (
192
185
options : {
193
186
actionCreator ?: never
@@ -210,19 +203,22 @@ interface RemoveListenerOverloads<
210
203
( type : string , listener : ActionListener < AnyAction , S , D > ) : boolean
211
204
}
212
205
206
+ /** A "pre-typed" version of `addListenerAction`, so the listener args are well-typed */
213
207
export type TypedAddListenerAction <
214
208
S ,
215
209
D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction > ,
216
210
Payload = ListenerEntry < S , D > ,
217
211
T extends string = 'actionListenerMiddleware/add'
218
212
> = BaseActionCreator < Payload , T > &
219
- AddListenerOverloads < PayloadAction < Payload > , S , D >
213
+ AddListenerOverloads < PayloadAction < Payload , T > , S , D >
220
214
215
+ /** A "pre-typed" version of `middleware.addListener`, so the listener args are well-typed */
221
216
export type TypedAddListener <
222
217
S ,
223
218
D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction >
224
219
> = AddListenerOverloads < Unsubscribe , S , D >
225
220
221
+ /** @internal An single listener entry */
226
222
type ListenerEntry <
227
223
S = unknown ,
228
224
D extends Dispatch < AnyAction > = Dispatch < AnyAction >
@@ -235,16 +231,13 @@ type ListenerEntry<
235
231
predicate : ListenerPredicate < AnyAction , S >
236
232
}
237
233
234
+ /** A "pre-typed" version of `createListenerEntry`, so the listener args are well-typed */
238
235
export type TypedCreateListenerEntry <
239
236
S ,
240
237
D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction >
241
238
> = AddListenerOverloads < ListenerEntry < S , D > , S , D >
242
239
243
- export type TypedAddListenerPrepareFunction <
244
- S ,
245
- D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction >
246
- > = AddListenerOverloads < { payload : ListenerEntry < S , D > } , S , D >
247
-
240
+ // A shorthand form of the accepted args, solely so that `createListenerEntry` has validly-typed conditional logic when checking the options contents
248
241
type FallbackAddListenerOptions = (
249
242
| { actionCreator : TypedActionCreator < string > }
250
243
| { type : string }
@@ -253,6 +246,7 @@ type FallbackAddListenerOptions = (
253
246
) &
254
247
ActionListenerOptions & { listener : ActionListener < any , any , any > }
255
248
249
+ /** Accepts the possible options for creating a listener, and returns a formatted listener entry */
256
250
export const createListenerEntry : TypedCreateListenerEntry < unknown > = (
257
251
options : FallbackAddListenerOptions
258
252
) => {
@@ -336,6 +330,7 @@ export const addListenerAction = createAction(
336
330
'actionListenerMiddleware/add' ,
337
331
function prepare ( options : unknown ) {
338
332
const entry = createListenerEntry (
333
+ // Fake out TS here
339
334
options as Parameters < AddListenerOverloads < unknown > > [ 0 ]
340
335
)
341
336
@@ -406,14 +401,6 @@ export function createActionListenerMiddleware<
406
401
D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction > ,
407
402
ExtraArgument = unknown
408
403
> ( middlewareOptions : CreateListenerMiddlewareOptions < ExtraArgument > = { } ) {
409
- type ListenerEntry = ActionListenerOptions & {
410
- id : string
411
- listener : ActionListener < any , S , D >
412
- unsubscribe : ( ) = > void
413
- type ?: string
414
- predicate : ListenerPredicate < any , any >
415
- }
416
-
417
404
const listenerMap = new Map < string , ListenerEntry > ( )
418
405
const { extra , onError = defaultErrorHandler } = middlewareOptions
419
406
@@ -434,7 +421,15 @@ export function createActionListenerMiddleware<
434
421
D
435
422
> = ( api ) => ( next ) => ( action ) => {
436
423
if ( addListenerAction . match ( action ) ) {
437
- return insertEntry ( action . payload )
424
+ let entry = findListenerEntry (
425
+ ( existingEntry ) => existingEntry . listener === action . payload . listener
426
+ )
427
+
428
+ if ( ! entry ) {
429
+ entry = action . payload
430
+ }
431
+
432
+ return insertEntry ( entry )
438
433
}
439
434
if ( removeListenerAction . match ( action ) ) {
440
435
removeListener ( action . payload . type , action . payload . listener )
@@ -479,6 +474,9 @@ export function createActionListenerMiddleware<
479
474
currentPhase,
480
475
extra,
481
476
unsubscribe : entry . unsubscribe ,
477
+ subscribe : ( ) => {
478
+ listenerMap . set ( entry . id , entry )
479
+ } ,
482
480
} )
483
481
} catch ( listenerError ) {
484
482
safelyNotifyError ( onError , listenerError )
0 commit comments