Skip to content

Commit

Permalink
Merge pull request #4517 from antondalgren/add-extra-args-to-listener…
Browse files Browse the repository at this point in the history
…-middleware-with-types
  • Loading branch information
markerikson authored Jul 25, 2024
2 parents e33130e + e1256f3 commit ae1dcbf
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 43 deletions.
5 changes: 4 additions & 1 deletion docs/api/createListenerMiddleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -488,11 +488,14 @@ To fix this, the middleware provides types for defining "pre-typed" versions of
import { createListenerMiddleware, addListener } from '@reduxjs/toolkit'
import type { RootState, AppDispatch } from './store'

declare type ExtraArgument = {foo: string};

export const listenerMiddleware = createListenerMiddleware()

export const startAppListening = listenerMiddleware.startListening.withTypes<
RootState,
AppDispatch
AppDispatch,
ExtraArgument
>()

export const addAppListener = addListener.withTypes<RootState, AppDispatch>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type AppThunk<ThunkReturnType = void> = ThunkAction<
unknown,
Action
>
type ExtraArgument = { foo: string }

describe('listenerMiddleware.withTypes<RootState, AppDispatch>()', () => {
const listenerMiddleware = createListenerMiddleware()
Expand All @@ -77,11 +78,12 @@ describe('listenerMiddleware.withTypes<RootState, AppDispatch>()', () => {
test('startListening.withTypes', () => {
const startAppListening = listenerMiddleware.startListening.withTypes<
RootState,
AppDispatch
AppDispatch,
ExtraArgument
>()

expectTypeOf(startAppListening).toEqualTypeOf<
TypedStartListening<RootState, AppDispatch>
TypedStartListening<RootState, AppDispatch, ExtraArgument>
>()

startAppListening({
Expand All @@ -102,6 +104,8 @@ describe('listenerMiddleware.withTypes<RootState, AppDispatch>()', () => {

expectTypeOf(stateCurrent).toEqualTypeOf<RootState>()

expectTypeOf(listenerApi.extra).toEqualTypeOf<ExtraArgument>()

timeout = 1
takeResult = await listenerApi.take(increment.match, timeout)

Expand All @@ -111,10 +115,10 @@ describe('listenerMiddleware.withTypes<RootState, AppDispatch>()', () => {
})

test('addListener.withTypes', () => {
const addAppListener = addListener.withTypes<RootState, AppDispatch>()
const addAppListener = addListener.withTypes<RootState, AppDispatch, ExtraArgument>()

expectTypeOf(addAppListener).toEqualTypeOf<
TypedAddListener<RootState, AppDispatch>
TypedAddListener<RootState, AppDispatch, ExtraArgument>
>()

store.dispatch(
Expand All @@ -126,27 +130,30 @@ describe('listenerMiddleware.withTypes<RootState, AppDispatch>()', () => {
expectTypeOf(state).toEqualTypeOf<RootState>()

expectTypeOf(listenerApi.dispatch).toEqualTypeOf<AppDispatch>()

expectTypeOf(listenerApi.extra).toEqualTypeOf<ExtraArgument>()
},
}),
)
})

test('removeListener.withTypes', () => {
const removeAppListener = removeListener.withTypes<RootState, AppDispatch>()
const removeAppListener = removeListener.withTypes<RootState, AppDispatch, ExtraArgument>()

expectTypeOf(removeAppListener).toEqualTypeOf<
TypedRemoveListener<RootState, AppDispatch>
TypedRemoveListener<RootState, AppDispatch, ExtraArgument>
>()
})

test('stopListening.withTypes', () => {
const stopAppListening = listenerMiddleware.stopListening.withTypes<
RootState,
AppDispatch
AppDispatch,
ExtraArgument
>()

expectTypeOf(stopAppListening).toEqualTypeOf<
TypedStopListening<RootState, AppDispatch>
TypedStopListening<RootState, AppDispatch, ExtraArgument>
>()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,25 @@ type AppThunk<ThunkReturnType = void> = ThunkAction<
Action
>

type ExtraArgument = { foo: string }

const listenerMiddleware = createListenerMiddleware()

const startAppListening = listenerMiddleware.startListening.withTypes<
RootState,
AppDispatch
AppDispatch,
ExtraArgument
>()

const stopAppListening = listenerMiddleware.stopListening.withTypes<
RootState,
AppDispatch
AppDispatch,
ExtraArgument
>()

const addAppListener = addListener.withTypes<RootState, AppDispatch>()
const addAppListener = addListener.withTypes<RootState, AppDispatch, ExtraArgument>()

const removeAppListener = removeListener.withTypes<RootState, AppDispatch>()
const removeAppListener = removeListener.withTypes<RootState, AppDispatch, ExtraArgument>()

describe('startAppListening.withTypes', () => {
test('should return startListening', () => {
Expand Down
84 changes: 54 additions & 30 deletions packages/toolkit/src/listenerMiddleware/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,11 +506,12 @@ export type RemoveListenerOverloads<
unknown,
UnknownAction
>,
ExtraArgument = unknown,
> = AddListenerOverloads<
boolean,
StateType,
DispatchType,
any,
ExtraArgument,
UnsubscribeListenerOptions
>

Expand Down Expand Up @@ -551,22 +552,23 @@ export type TypedAddListener<
> & {
/**
* Creates a "pre-typed" version of `addListener`
* where the `state` and `dispatch` types are predefined.
* where the `state`, `dispatch` and `extra` types are predefined.
*
* This allows you to set the `state` and `dispatch` types once,
* This allows you to set the `state`, `dispatch` and `extra` types once,
* eliminating the need to specify them with every `addListener` call.
*
* @returns A pre-typed `addListener` with the state and dispatch types already defined.
* @returns A pre-typed `addListener` with the state, dispatch and extra types already defined.
*
* @example
* ```ts
* import { addListener } from '@reduxjs/toolkit'
*
* export const addAppListener = addListener.withTypes<RootState, AppDispatch>()
* export const addAppListener = addListener.withTypes<RootState, AppDispatch, ExtraArguments>()
* ```
*
* @template OverrideStateType - The specific type of state the middleware listener operates on.
* @template OverrideDispatchType - The specific type of the dispatch function.
* @template OverrideExtraArgument - The specific type of the extra object.
*
* @since 2.1.0
*/
Expand All @@ -576,8 +578,9 @@ export type TypedAddListener<
OverrideStateType,
unknown,
UnknownAction
>,
>() => TypedAddListener<OverrideStateType, OverrideDispatchType>
>,
OverrideExtraArgument = unknown,
>() => TypedAddListener<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
}

/**
Expand All @@ -592,34 +595,41 @@ export type TypedRemoveListener<
unknown,
UnknownAction
>,
ExtraArgument = unknown,
Payload = ListenerEntry<StateType, DispatchType>,
T extends string = 'listenerMiddleware/remove',
> = BaseActionCreator<Payload, T> &
AddListenerOverloads<
PayloadAction<Payload, T>,
StateType,
DispatchType,
any,
ExtraArgument,
UnsubscribeListenerOptions
> & {
/**
* Creates a "pre-typed" version of `removeListener`
* where the `state` and `dispatch` types are predefined.
* where the `state`, `dispatch` and `extra` types are predefined.
*
* This allows you to set the `state` and `dispatch` types once,
* This allows you to set the `state`, `dispatch` and `extra` types once,
* eliminating the need to specify them with every `removeListener` call.
*
* @returns A pre-typed `removeListener` with the state and dispatch types already defined.
* @returns A pre-typed `removeListener` with the state, dispatch and extra
* types already defined.
*
* @example
* ```ts
* import { removeListener } from '@reduxjs/toolkit'
*
* export const removeAppListener = removeListener.withTypes<RootState, AppDispatch>()
* export const removeAppListener = removeListener.withTypes<
* RootState,
* AppDispatch,
* ExtraArguments
* >()
* ```
*
* @template OverrideStateType - The specific type of state the middleware listener operates on.
* @template OverrideDispatchType - The specific type of the dispatch function.
* @template OverrideExtraArgument - The specific type of the extra object.
*
* @since 2.1.0
*/
Expand All @@ -630,7 +640,8 @@ export type TypedRemoveListener<
unknown,
UnknownAction
>,
>() => TypedRemoveListener<OverrideStateType, OverrideDispatchType>
OverrideExtraArgument = unknown,
>() => TypedRemoveListener<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
}

/**
Expand All @@ -655,13 +666,13 @@ export type TypedStartListening<
/**
* Creates a "pre-typed" version of
* {@linkcode ListenerMiddlewareInstance.startListening startListening}
* where the `state` and `dispatch` types are predefined.
* where the `state`, `dispatch` and `extra` types are predefined.
*
* This allows you to set the `state` and `dispatch` types once,
* This allows you to set the `state`, `dispatch` and `extra` types once,
* eliminating the need to specify them with every
* {@linkcode ListenerMiddlewareInstance.startListening startListening} call.
*
* @returns A pre-typed `startListening` with the state and dispatch types already defined.
* @returns A pre-typed `startListening` with the state, dispatch and extra types already defined.
*
* @example
* ```ts
Expand All @@ -671,12 +682,14 @@ export type TypedStartListening<
*
* export const startAppListening = listenerMiddleware.startListening.withTypes<
* RootState,
* AppDispatch
* AppDispatch,
* ExtraArguments
* >()
* ```
*
* @template OverrideStateType - The specific type of state the middleware listener operates on.
* @template OverrideDispatchType - The specific type of the dispatch function.
* @template OverrideExtraArgument - The specific type of the extra object.
*
* @since 2.1.0
*/
Expand All @@ -687,7 +700,8 @@ export type TypedStartListening<
unknown,
UnknownAction
>,
>() => TypedStartListening<OverrideStateType, OverrideDispatchType>
OverrideExtraArgument = unknown,
>() => TypedStartListening<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
}

/**
Expand All @@ -702,17 +716,18 @@ export type TypedStopListening<
unknown,
UnknownAction
>,
> = RemoveListenerOverloads<StateType, DispatchType> & {
ExtraArgument = unknown,
> = RemoveListenerOverloads<StateType, DispatchType, ExtraArgument> & {
/**
* Creates a "pre-typed" version of
* {@linkcode ListenerMiddlewareInstance.stopListening stopListening}
* where the `state` and `dispatch` types are predefined.
* where the `state`, `dispatch` and `extra` types are predefined.
*
* This allows you to set the `state` and `dispatch` types once,
* This allows you to set the `state`, `dispatch` and `extra` types once,
* eliminating the need to specify them with every
* {@linkcode ListenerMiddlewareInstance.stopListening stopListening} call.
*
* @returns A pre-typed `stopListening` with the state and dispatch types already defined.
* @returns A pre-typed `stopListening` with the state, dispatch and extra types already defined.
*
* @example
* ```ts
Expand All @@ -722,12 +737,14 @@ export type TypedStopListening<
*
* export const stopAppListening = listenerMiddleware.stopListening.withTypes<
* RootState,
* AppDispatch
* AppDispatch,
* ExtraArguments
* >()
* ```
*
* @template OverrideStateType - The specific type of state the middleware listener operates on.
* @template OverrideDispatchType - The specific type of the dispatch function.
* @template OverrideExtraArgument - The specific type of the extra object.
*
* @since 2.1.0
*/
Expand All @@ -738,7 +755,8 @@ export type TypedStopListening<
unknown,
UnknownAction
>,
>() => TypedStopListening<OverrideStateType, OverrideDispatchType>
OverrideExtraArgument = unknown,
>() => TypedStopListening<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
}

/**
Expand All @@ -753,32 +771,37 @@ export type TypedCreateListenerEntry<
unknown,
UnknownAction
>,
ExtraArgument = unknown,
> = AddListenerOverloads<
ListenerEntry<StateType, DispatchType>,
StateType,
DispatchType
DispatchType,
ExtraArgument
> & {
/**
* Creates a "pre-typed" version of `createListenerEntry`
* where the `state` and `dispatch` types are predefined.
* where the `state`, `dispatch` and `extra` types are predefined.
*
* This allows you to set the `state` and `dispatch` types once, eliminating
* This allows you to set the `state`, `dispatch` and `extra` types once, eliminating
* the need to specify them with every `createListenerEntry` call.
*
* @returns A pre-typed `createListenerEntry` with the state and dispatch types already defined.
* @returns A pre-typed `createListenerEntry` with the state, dispatch and extra
* types already defined.
*
* @example
* ```ts
* import { createListenerEntry } from '@reduxjs/toolkit'
*
* export const createAppListenerEntry = createListenerEntry.withTypes<
* RootState,
* AppDispatch
* AppDispatch,
* ExtraArguments
* >()
* ```
*
* @template OverrideStateType - The specific type of state the middleware listener operates on.
* @template OverrideDispatchType - The specific type of the dispatch function.
* @template OverrideExtraArgument - The specific type of the extra object.
*
* @since 2.1.0
*/
Expand All @@ -789,7 +812,8 @@ export type TypedCreateListenerEntry<
unknown,
UnknownAction
>,
>() => TypedStopListening<OverrideStateType, OverrideDispatchType>
OverrideExtraArgument = unknown,
>() => TypedStopListening<OverrideStateType, OverrideDispatchType, OverrideExtraArgument>
}

/**
Expand Down

0 comments on commit ae1dcbf

Please sign in to comment.