@@ -59,25 +59,6 @@ function assertFunction(
5959 }
6060}
6161
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-
8162type Unsubscribe = ( ) => void
8263
8364type GuardedType < T > = T extends ( x : any , ...args : unknown [ ] ) => x is infer T
@@ -110,6 +91,7 @@ export interface ActionListenerMiddlewareAPI<S, D extends Dispatch<AnyAction>>
11091 extends MiddlewareAPI < D , S > {
11192 getOriginalState : ( ) => S
11293 unsubscribe ( ) : void
94+ subscribe ( ) : void
11395 condition : ConditionFunction < S >
11496 currentPhase : MiddlewarePhase
11597 // TODO Figure out how to pass this through the other types correctly
@@ -146,11 +128,15 @@ export interface CreateListenerMiddlewareOptions<ExtraArgument = unknown> {
146128 onError ?: ListenerErrorHandler
147129}
148130
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+ */
149134interface AddListenerOverloads <
150135 Return ,
151136 S = unknown ,
152137 D extends Dispatch = ThunkDispatch < S , unknown , AnyAction >
153138> {
139+ /** Accepts a "listener predicate" that is also a TS type predicate for the action*/
154140 < MA extends AnyAction , LP extends ListenerPredicate < MA , S > > (
155141 options : {
156142 actionCreator ?: never
@@ -160,6 +146,8 @@ interface AddListenerOverloads<
160146 listener : ActionListener < ListenerPredicateGuardedActionType < LP > , S , D >
161147 } & ActionListenerOptions
162148 ) : Return
149+
150+ /** Accepts an RTK action creator, like `incrementByAmount` */
163151 < C extends TypedActionCreator < any > > (
164152 options : {
165153 actionCreator : C
@@ -169,6 +157,8 @@ interface AddListenerOverloads<
169157 listener : ActionListener < ReturnType < C > , S , D >
170158 } & ActionListenerOptions
171159 ) : Return
160+
161+ /** Accepts a specific action type string */
172162 < T extends string > (
173163 options : {
174164 actionCreator ?: never
@@ -178,6 +168,8 @@ interface AddListenerOverloads<
178168 listener : ActionListener < Action < T > , S , D >
179169 } & ActionListenerOptions
180170 ) : Return
171+
172+ /** Accepts an RTK matcher function, such as `incrementByAmount.match` */
181173 < MA extends AnyAction , M extends MatchFunction < MA > > (
182174 options : {
183175 actionCreator ?: never
@@ -188,6 +180,7 @@ interface AddListenerOverloads<
188180 } & ActionListenerOptions
189181 ) : Return
190182
183+ /** Accepts a "listener predicate" that just returns a boolean, no type assertion */
191184 < LP extends AnyActionListenerPredicate < S > > (
192185 options : {
193186 actionCreator ?: never
@@ -210,19 +203,22 @@ interface RemoveListenerOverloads<
210203 ( type : string , listener : ActionListener < AnyAction , S , D > ) : boolean
211204}
212205
206+ /** A "pre-typed" version of `addListenerAction`, so the listener args are well-typed */
213207export type TypedAddListenerAction <
214208 S ,
215209 D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction > ,
216210 Payload = ListenerEntry < S , D > ,
217211 T extends string = 'actionListenerMiddleware/add'
218212> = BaseActionCreator < Payload , T > &
219- AddListenerOverloads < PayloadAction < Payload > , S , D >
213+ AddListenerOverloads < PayloadAction < Payload , T > , S , D >
220214
215+ /** A "pre-typed" version of `middleware.addListener`, so the listener args are well-typed */
221216export type TypedAddListener <
222217 S ,
223218 D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction >
224219> = AddListenerOverloads < Unsubscribe , S , D >
225220
221+ /** @internal An single listener entry */
226222type ListenerEntry <
227223 S = unknown ,
228224 D extends Dispatch < AnyAction > = Dispatch < AnyAction >
@@ -235,16 +231,13 @@ type ListenerEntry<
235231 predicate : ListenerPredicate < AnyAction , S >
236232}
237233
234+ /** A "pre-typed" version of `createListenerEntry`, so the listener args are well-typed */
238235export type TypedCreateListenerEntry <
239236 S ,
240237 D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction >
241238> = AddListenerOverloads < ListenerEntry < S , D > , S , D >
242239
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
248241type FallbackAddListenerOptions = (
249242 | { actionCreator : TypedActionCreator < string > }
250243 | { type : string }
@@ -253,6 +246,7 @@ type FallbackAddListenerOptions = (
253246) &
254247 ActionListenerOptions & { listener : ActionListener < any , any , any > }
255248
249+ /** Accepts the possible options for creating a listener, and returns a formatted listener entry */
256250export const createListenerEntry : TypedCreateListenerEntry < unknown > = (
257251 options : FallbackAddListenerOptions
258252) => {
@@ -336,6 +330,7 @@ export const addListenerAction = createAction(
336330 'actionListenerMiddleware/add' ,
337331 function prepare ( options : unknown ) {
338332 const entry = createListenerEntry (
333+ // Fake out TS here
339334 options as Parameters < AddListenerOverloads < unknown > > [ 0 ]
340335 )
341336
@@ -406,14 +401,6 @@ export function createActionListenerMiddleware<
406401 D extends Dispatch < AnyAction > = ThunkDispatch < S , unknown , AnyAction > ,
407402 ExtraArgument = unknown
408403> ( 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-
417404 const listenerMap = new Map < string , ListenerEntry > ( )
418405 const { extra , onError = defaultErrorHandler } = middlewareOptions
419406
@@ -434,7 +421,15 @@ export function createActionListenerMiddleware<
434421 D
435422 > = ( api ) => ( next ) => ( action ) => {
436423 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 )
438433 }
439434 if ( removeListenerAction . match ( action ) ) {
440435 removeListener ( action . payload . type , action . payload . listener )
@@ -479,6 +474,9 @@ export function createActionListenerMiddleware<
479474 currentPhase,
480475 extra,
481476 unsubscribe : entry . unsubscribe ,
477+ subscribe : ( ) => {
478+ listenerMap . set ( entry . id , entry )
479+ } ,
482480 } )
483481 } catch ( listenerError ) {
484482 safelyNotifyError ( onError , listenerError )
0 commit comments