Skip to content

Commit da3f6ed

Browse files
authored
Merge pull request #3372 from EskiMojo14/listener-action
fixes #3371
2 parents ebe1777 + 0c6cd6e commit da3f6ed

File tree

5 files changed

+45
-7
lines changed

5 files changed

+45
-7
lines changed

packages/toolkit/src/createAction.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export type PayloadActionCreator<
224224
* A utility function to create an action creator for the given action type
225225
* string. The action creator accepts a single argument, which will be included
226226
* in the action object as a field called payload. The action creator function
227-
* will also have its toString() overriden so that it returns the action type,
227+
* will also have its toString() overridden so that it returns the action type,
228228
* allowing it to be used in reducer logic that is looking for that action type.
229229
*
230230
* @param type The action type to use for created actions.
@@ -241,7 +241,7 @@ export function createAction<P = void, T extends string = string>(
241241
* A utility function to create an action creator for the given action type
242242
* string. The action creator accepts a single argument, which will be included
243243
* in the action object as a field called payload. The action creator function
244-
* will also have its toString() overriden so that it returns the action type,
244+
* will also have its toString() overridden so that it returns the action type,
245245
* allowing it to be used in reducer logic that is looking for that action type.
246246
*
247247
* @param type The action type to use for created actions.
@@ -286,15 +286,25 @@ export function createAction(type: string, prepareAction?: Function): any {
286286
return actionCreator
287287
}
288288

289+
/**
290+
* Returns true if value is a plain object with a `type` property.
291+
*/
292+
export function isAction(action: unknown): action is Action<unknown> {
293+
return isPlainObject(action) && 'type' in action
294+
}
295+
296+
/**
297+
* Returns true if value is an action with a string type and valid Flux Standard Action keys.
298+
*/
289299
export function isFSA(action: unknown): action is {
290300
type: string
291301
payload?: unknown
292302
error?: unknown
293303
meta?: unknown
294304
} {
295305
return (
296-
isPlainObject(action) &&
297-
typeof (action as any).type === 'string' &&
306+
isAction(action) &&
307+
typeof action.type === 'string' &&
298308
Object.keys(action).every(isValidKey)
299309
)
300310
}

packages/toolkit/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export {
3939
// js
4040
createAction,
4141
getType,
42+
isAction,
43+
isFSA as isFluxStandardAction,
4244
} from './createAction'
4345
export type {
4446
// types

packages/toolkit/src/listenerMiddleware/index.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Dispatch, AnyAction, MiddlewareAPI } from 'redux'
22
import type { ThunkDispatch } from 'redux-thunk'
3-
import { createAction } from '../createAction'
3+
import { createAction, isAction } from '../createAction'
44
import { nanoid } from '../nanoid'
55

66
import type {
@@ -426,6 +426,11 @@ export function createListenerMiddleware<
426426

427427
const middleware: ListenerMiddleware<S, D, ExtraArgument> =
428428
(api) => (next) => (action) => {
429+
if (!isAction(action)) {
430+
// we only want to notify listeners for action objects
431+
return next(action)
432+
}
433+
429434
if (addListener.match(action)) {
430435
return startListening(action.payload)
431436
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ describe('fork', () => {
367367
},
368368
})
369369

370-
store.dispatch(increment)
370+
store.dispatch(increment())
371371

372372
expect(await deferredResult).toBe(listenerCompleted)
373373
})

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

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createAction, getType } from '@reduxjs/toolkit'
1+
import { createAction, getType, isAction } from '@reduxjs/toolkit'
22

33
describe('createAction', () => {
44
it('should create an action', () => {
@@ -122,6 +122,27 @@ describe('createAction', () => {
122122
})
123123
})
124124

125+
describe('isAction', () => {
126+
it('should only return true for plain objects with a type property', () => {
127+
const actionCreator = createAction('anAction')
128+
class Action {
129+
type = 'totally an action'
130+
}
131+
const testCases: [action: unknown, expected: boolean][] = [
132+
[{ type: 'an action' }, true],
133+
[{ type: 'more props', extra: true }, true],
134+
[actionCreator(), true],
135+
[actionCreator, false],
136+
[Promise.resolve({ type: 'an action' }), false],
137+
[new Action(), false],
138+
['a string', false],
139+
]
140+
for (const [action, expected] of testCases) {
141+
expect(isAction(action)).toBe(expected)
142+
}
143+
})
144+
})
145+
125146
describe('getType', () => {
126147
it('should return the action type', () => {
127148
const actionCreator = createAction('A_TYPE')

0 commit comments

Comments
 (0)