Skip to content

Commit

Permalink
Merge pull request #3372 from EskiMojo14/listener-action
Browse files Browse the repository at this point in the history
fixes #3371
  • Loading branch information
markerikson committed Apr 18, 2023
2 parents ebe1777 + 0c6cd6e commit da3f6ed
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 7 deletions.
18 changes: 14 additions & 4 deletions packages/toolkit/src/createAction.ts
Expand Up @@ -224,7 +224,7 @@ export type PayloadActionCreator<
* A utility function to create an action creator for the given action type
* string. The action creator accepts a single argument, which will be included
* in the action object as a field called payload. The action creator function
* will also have its toString() overriden so that it returns the action type,
* will also have its toString() overridden so that it returns the action type,
* allowing it to be used in reducer logic that is looking for that action type.
*
* @param type The action type to use for created actions.
Expand All @@ -241,7 +241,7 @@ export function createAction<P = void, T extends string = string>(
* A utility function to create an action creator for the given action type
* string. The action creator accepts a single argument, which will be included
* in the action object as a field called payload. The action creator function
* will also have its toString() overriden so that it returns the action type,
* will also have its toString() overridden so that it returns the action type,
* allowing it to be used in reducer logic that is looking for that action type.
*
* @param type The action type to use for created actions.
Expand Down Expand Up @@ -286,15 +286,25 @@ export function createAction(type: string, prepareAction?: Function): any {
return actionCreator
}

/**
* Returns true if value is a plain object with a `type` property.
*/
export function isAction(action: unknown): action is Action<unknown> {
return isPlainObject(action) && 'type' in action
}

/**
* Returns true if value is an action with a string type and valid Flux Standard Action keys.
*/
export function isFSA(action: unknown): action is {
type: string
payload?: unknown
error?: unknown
meta?: unknown
} {
return (
isPlainObject(action) &&
typeof (action as any).type === 'string' &&
isAction(action) &&
typeof action.type === 'string' &&
Object.keys(action).every(isValidKey)
)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/toolkit/src/index.ts
Expand Up @@ -39,6 +39,8 @@ export {
// js
createAction,
getType,
isAction,
isFSA as isFluxStandardAction,
} from './createAction'
export type {
// types
Expand Down
7 changes: 6 additions & 1 deletion packages/toolkit/src/listenerMiddleware/index.ts
@@ -1,6 +1,6 @@
import type { Dispatch, AnyAction, MiddlewareAPI } from 'redux'
import type { ThunkDispatch } from 'redux-thunk'
import { createAction } from '../createAction'
import { createAction, isAction } from '../createAction'
import { nanoid } from '../nanoid'

import type {
Expand Down Expand Up @@ -426,6 +426,11 @@ export function createListenerMiddleware<

const middleware: ListenerMiddleware<S, D, 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)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/toolkit/src/listenerMiddleware/tests/fork.test.ts
Expand Up @@ -367,7 +367,7 @@ describe('fork', () => {
},
})

store.dispatch(increment)
store.dispatch(increment())

expect(await deferredResult).toBe(listenerCompleted)
})
Expand Down
23 changes: 22 additions & 1 deletion packages/toolkit/src/tests/createAction.test.ts
@@ -1,4 +1,4 @@
import { createAction, getType } from '@reduxjs/toolkit'
import { createAction, getType, isAction } from '@reduxjs/toolkit'

describe('createAction', () => {
it('should create an action', () => {
Expand Down Expand Up @@ -122,6 +122,27 @@ describe('createAction', () => {
})
})

describe('isAction', () => {
it('should only return true for plain objects with a type property', () => {
const actionCreator = createAction('anAction')
class Action {
type = 'totally an action'
}
const testCases: [action: unknown, expected: boolean][] = [
[{ type: 'an action' }, true],
[{ type: 'more props', extra: true }, true],
[actionCreator(), true],
[actionCreator, false],
[Promise.resolve({ type: 'an action' }), false],
[new Action(), false],
['a string', false],
]
for (const [action, expected] of testCases) {
expect(isAction(action)).toBe(expected)
}
})
})

describe('getType', () => {
it('should return the action type', () => {
const actionCreator = createAction('A_TYPE')
Expand Down

0 comments on commit da3f6ed

Please sign in to comment.