Skip to content

Commit

Permalink
Merge pull request #4049 from aryaemami59/withTypes
Browse files Browse the repository at this point in the history
Introduce pre-typed hooks via `listenerMiddleware.withTypes<RootState, AppDispatch>()` method
  • Loading branch information
EskiMojo14 authored Jan 12, 2024
2 parents 904ef2b + 763a598 commit 0ffabd9
Show file tree
Hide file tree
Showing 10 changed files with 704 additions and 165 deletions.
13 changes: 4 additions & 9 deletions docs/api/createListenerMiddleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -486,21 +486,16 @@ To fix this, the middleware provides types for defining "pre-typed" versions of
```ts no-transpile
// listenerMiddleware.ts
import { createListenerMiddleware, addListener } from '@reduxjs/toolkit'
import type { TypedStartListening, TypedAddListener } from '@reduxjs/toolkit'

import type { RootState, AppDispatch } from './store'

export const listenerMiddleware = createListenerMiddleware()

export type AppStartListening = TypedStartListening<RootState, AppDispatch>

export const startAppListening =
listenerMiddleware.startListening as AppStartListening

export const addAppListener = addListener as TypedAddListener<
export const startAppListening = listenerMiddleware.startListening.withTypes<
RootState,
AppDispatch
>
>()

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

Then import and use those pre-typed methods in your components.
Expand Down
2 changes: 1 addition & 1 deletion packages/toolkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"@testing-library/user-event": "^13.1.5",
"@types/json-stringify-safe": "^5.0.0",
"@types/nanoid": "^2.1.0",
"@types/node": "^10.14.4",
"@types/node": "^20.11.0",
"@types/query-string": "^6.3.0",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
Expand Down
174 changes: 95 additions & 79 deletions packages/toolkit/src/listenerMiddleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,72 @@ import type { ThunkDispatch } from 'redux-thunk'
import { createAction } from '../createAction'
import { nanoid } from '../nanoid'

import { find } from '../utils'
import {
TaskAbortError,
listenerCancelled,
listenerCompleted,
taskCancelled,
taskCompleted,
} from './exceptions'
import {
createDelay,
createPause,
raceWithSignal,
runTask,
validateActive,
} from './task'
import type {
ListenerMiddleware,
ListenerMiddlewareInstance,
AbortSignalWithReason,
AddListenerOverloads,
AnyListenerPredicate,
CreateListenerMiddlewareOptions,
TypedAddListener,
TypedCreateListenerEntry,
FallbackAddListenerOptions,
ForkOptions,
ForkedTask,
ForkedTaskExecutor,
ListenerEntry,
ListenerErrorHandler,
UnsubscribeListener,
TakePattern,
ListenerErrorInfo,
ForkedTaskExecutor,
ForkedTask,
TypedRemoveListener,
ListenerMiddleware,
ListenerMiddlewareInstance,
TakePattern,
TaskResult,
AbortSignalWithReason,
TypedAddListener,
TypedCreateListenerEntry,
TypedRemoveListener,
UnsubscribeListener,
UnsubscribeListenerOptions,
ForkOptions,
} from './types'
import {
abortControllerWithReason,
addAbortSignalListener,
assertFunction,
catchRejection,
} from './utils'
import {
listenerCancelled,
listenerCompleted,
TaskAbortError,
taskCancelled,
taskCompleted,
} from './exceptions'
import {
runTask,
validateActive,
createPause,
createDelay,
raceWithSignal,
} from './task'
import { find } from '../utils'
export { TaskAbortError } from './exceptions'
export type {
ListenerEffect,
ListenerMiddleware,
ListenerEffectAPI,
ListenerMiddlewareInstance,
AsyncTaskExecutor,
CreateListenerMiddlewareOptions,
ListenerErrorHandler,
TypedStartListening,
TypedAddListener,
TypedStopListening,
TypedRemoveListener,
UnsubscribeListener,
UnsubscribeListenerOptions,
ForkedTaskExecutor,
ForkedTask,
ForkedTaskAPI,
AsyncTaskExecutor,
ForkedTaskExecutor,
ListenerEffect,
ListenerEffectAPI,
ListenerErrorHandler,
ListenerMiddleware,
ListenerMiddlewareInstance,
SyncTaskExecutor,
TaskCancelled,
TaskRejected,
TaskResolved,
TaskResult,
TypedAddListener,
TypedRemoveListener,
TypedStartListening,
TypedStopListening,
UnsubscribeListener,
UnsubscribeListenerOptions,
} from './types'

//Overly-aggressive byte-shaving
Expand Down Expand Up @@ -215,25 +215,27 @@ const getListenerEntryPropsFrom = (options: FallbackAddListenerOptions) => {
}

/** Accepts the possible options for creating a listener, and returns a formatted listener entry */
export const createListenerEntry: TypedCreateListenerEntry<unknown> = (
options: FallbackAddListenerOptions
) => {
const { type, predicate, effect } = getListenerEntryPropsFrom(options)

const id = nanoid()
const entry: ListenerEntry<unknown> = {
id,
effect,
type,
predicate,
pending: new Set<AbortController>(),
unsubscribe: () => {
throw new Error('Unsubscribe not initialized')
},
}
export const createListenerEntry: TypedCreateListenerEntry<unknown> =
Object.assign(
(options: FallbackAddListenerOptions) => {
const { type, predicate, effect } = getListenerEntryPropsFrom(options)

const id = nanoid()
const entry: ListenerEntry<unknown> = {
id,
effect,
type,
predicate,
pending: new Set<AbortController>(),
unsubscribe: () => {
throw new Error('Unsubscribe not initialized')
},
}

return entry
}
return entry
},
{ withTypes: () => createListenerEntry }
) as unknown as TypedCreateListenerEntry<unknown>

const cancelActiveListeners = (
entry: ListenerEntry<unknown, Dispatch<UnknownAction>>
Expand Down Expand Up @@ -279,9 +281,9 @@ const safelyNotifyError = (
/**
* @public
*/
export const addListener = createAction(
`${alm}/add`
) as TypedAddListener<unknown>
export const addListener = Object.assign(createAction(`${alm}/add`), {
withTypes: () => addListener,
}) as unknown as TypedAddListener<unknown>

/**
* @public
Expand All @@ -291,9 +293,9 @@ export const clearAllListeners = createAction(`${alm}/removeAll`)
/**
* @public
*/
export const removeListener = createAction(
`${alm}/remove`
) as TypedRemoveListener<unknown>
export const removeListener = Object.assign(createAction(`${alm}/remove`), {
withTypes: () => removeListener,
}) as unknown as TypedRemoveListener<unknown>

const defaultErrorHandler: ListenerErrorHandler = (...args: unknown[]) => {
console.error(`${alm}/error`, ...args)
Expand All @@ -302,11 +304,17 @@ const defaultErrorHandler: ListenerErrorHandler = (...args: unknown[]) => {
/**
* @public
*/
export function createListenerMiddleware<
S = unknown,
D extends Dispatch<Action> = ThunkDispatch<S, unknown, UnknownAction>,
export const createListenerMiddleware = <
StateType = unknown,
DispatchType extends Dispatch<Action> = ThunkDispatch<
StateType,
unknown,
UnknownAction
>,
ExtraArgument = unknown
>(middlewareOptions: CreateListenerMiddlewareOptions<ExtraArgument> = {}) {
>(
middlewareOptions: CreateListenerMiddlewareOptions<ExtraArgument> = {}
) => {
const listenerMap = new Map<string, ListenerEntry>()
const { extra, onError = defaultErrorHandler } = middlewareOptions

Expand All @@ -324,7 +332,7 @@ export function createListenerMiddleware<
}
}

const startListening = (options: FallbackAddListenerOptions) => {
const startListening = ((options: FallbackAddListenerOptions) => {
let entry = find(
Array.from(listenerMap.values()),
(existingEntry) => existingEntry.effect === options.effect
Expand All @@ -335,7 +343,11 @@ export function createListenerMiddleware<
}

return insertEntry(entry)
}
}) as AddListenerOverloads<any>

Object.assign(startListening, {
withTypes: () => startListening,
})

const stopListening = (
options: FallbackAddListenerOptions & UnsubscribeListenerOptions
Expand All @@ -361,15 +373,19 @@ export function createListenerMiddleware<
return !!entry
}

Object.assign(stopListening, {
withTypes: () => stopListening,
})

const notifyListener = async (
entry: ListenerEntry<unknown, Dispatch<UnknownAction>>,
action: unknown,
api: MiddlewareAPI,
getOriginalState: () => S
getOriginalState: () => StateType
) => {
const internalTaskController = new AbortController()
const take = createTakePattern(
startListening,
startListening as AddListenerOverloads<any>,
internalTaskController.signal
)
const autoJoinPromises: Promise<any>[] = []
Expand Down Expand Up @@ -433,15 +449,15 @@ export function createListenerMiddleware<

const clearListenerMiddleware = createClearListenerMiddleware(listenerMap)

const middleware: ListenerMiddleware<S, D, ExtraArgument> =
const middleware: ListenerMiddleware<StateType, DispatchType, ExtraArgument> =
(api) => (next) => (action) => {
if (!isAction(action)) {
// we only want to notify listeners for action objects
return next(action)
}

if (addListener.match(action)) {
return startListening(action.payload)
return startListening(action.payload as any)
}

if (clearAllListeners.match(action)) {
Expand All @@ -454,18 +470,18 @@ export function createListenerMiddleware<
}

// Need to get this state _before_ the reducer processes the action
let originalState: S | typeof INTERNAL_NIL_TOKEN = api.getState()
let originalState: StateType | typeof INTERNAL_NIL_TOKEN = api.getState()

// `getOriginalState` can only be called synchronously.
// @see https://github.com/reduxjs/redux-toolkit/discussions/1648#discussioncomment-1932820
const getOriginalState = (): S => {
const getOriginalState = (): StateType => {
if (originalState === INTERNAL_NIL_TOKEN) {
throw new Error(
`${alm}: getOriginalState can only be called synchronously`
)
}

return originalState as S
return originalState as StateType
}

let result: unknown
Expand All @@ -475,10 +491,10 @@ export function createListenerMiddleware<
result = next(action)

if (listenerMap.size > 0) {
let currentState = api.getState()
const currentState = api.getState()
// Work around ESBuild+TS transpilation issue
const listenerEntries = Array.from(listenerMap.values())
for (let entry of listenerEntries) {
for (const entry of listenerEntries) {
let runListener = false

try {
Expand Down Expand Up @@ -511,5 +527,5 @@ export function createListenerMiddleware<
startListening,
stopListening,
clearListeners: clearListenerMiddleware,
} as ListenerMiddlewareInstance<S, D, ExtraArgument>
} as ListenerMiddlewareInstance<StateType, DispatchType, ExtraArgument>
}
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ describe('createListenerMiddleware', () => {
typeof store.getState,
typeof store.dispatch
>,
'effect'
'effect' | 'withTypes'
>
][] = [
['predicate', { predicate: () => true }],
Expand Down Expand Up @@ -1760,3 +1760,4 @@ describe('createListenerMiddleware', () => {
})
})
})

Loading

0 comments on commit 0ffabd9

Please sign in to comment.