-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3414 from EskiMojo14/action-creator-middleware
fixes #3413
- Loading branch information
Showing
10 changed files
with
258 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
--- | ||
id: actionCreatorMiddleware | ||
title: Action Creator Middleware | ||
sidebar_label: Action Creator Middleware | ||
hide_title: true | ||
--- | ||
|
||
| ||
|
||
# Action Creator Middleware | ||
|
||
A custom middleware that detects if an action creator has been mistakenly dispatched, instead of being called before dispatching. | ||
|
||
A common mistake is to call `dispatch(actionCreator)` instead of `dispatch(actionCreator())`. | ||
This tends to "work" as the action creator has the static `type` property, but can lead to unexpected behaviour. | ||
|
||
## Options | ||
|
||
```ts no-transpile | ||
export interface ActionCreatorInvariantMiddlewareOptions { | ||
/** | ||
* The function to identify whether a value is an action creator. | ||
* The default checks for a function with a static type property and match method. | ||
*/ | ||
isActionCreator?: (action: unknown) => action is Function & { type?: unknown } | ||
} | ||
``` | ||
|
||
## Exports | ||
|
||
### `createActionCreatorInvariantMiddleware` | ||
|
||
Creates an instance of the action creator check middleware, with the given options. | ||
|
||
You will most likely not need to call this yourself, as `getDefaultMiddleware` already does so. | ||
Example: | ||
|
||
```ts | ||
// file: reducer.ts noEmit | ||
|
||
export default function (state = {}, action: any) { | ||
return state | ||
} | ||
|
||
// file: store.ts | ||
|
||
import { | ||
configureStore, | ||
createActionCreatorInvariantMiddleware, | ||
} from '@reduxjs/toolkit' | ||
import reducer from './reducer' | ||
|
||
// Augment middleware to consider all functions with a static type property to be action creators | ||
const isActionCreator = ( | ||
action: unknown | ||
): action is Function & { type: unknown } => | ||
typeof action === 'function' && 'type' in action | ||
|
||
const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({ | ||
isActionCreator, | ||
}) | ||
|
||
const store = configureStore({ | ||
reducer, | ||
middleware: [actionCreatorMiddleware], | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import type { Middleware } from 'redux' | ||
import { isActionCreator as isRTKAction } from './createAction' | ||
|
||
export interface ActionCreatorInvariantMiddlewareOptions { | ||
/** | ||
* The function to identify whether a value is an action creator. | ||
* The default checks for a function with a static type property and match method. | ||
*/ | ||
isActionCreator?: (action: unknown) => action is Function & { type?: unknown } | ||
} | ||
|
||
export function getMessage(type?: unknown) { | ||
const splitType = type ? `${type}`.split('/') : [] | ||
const actionName = splitType[splitType.length - 1] || 'actionCreator' | ||
return `Detected an action creator with type "${ | ||
type || 'unknown' | ||
}" being dispatched. | ||
Make sure you're calling the action creator before dispatching, i.e. \`dispatch(${actionName}())\` instead of \`dispatch(${actionName})\`. This is necessary even if the action has no payload.` | ||
} | ||
|
||
export function createActionCreatorInvariantMiddleware( | ||
options: ActionCreatorInvariantMiddlewareOptions = {} | ||
): Middleware { | ||
if (process.env.NODE_ENV === 'production') { | ||
return () => (next) => (action) => next(action) | ||
} | ||
const { isActionCreator = isRTKAction } = options | ||
return () => (next) => (action) => { | ||
if (isActionCreator(action)) { | ||
console.warn(getMessage(action.type)) | ||
} | ||
return next(action) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
packages/toolkit/src/tests/actionCreatorInvariantMiddleware.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import type { ActionCreatorInvariantMiddlewareOptions } from '@internal/actionCreatorInvariantMiddleware' | ||
import { getMessage } from '@internal/actionCreatorInvariantMiddleware' | ||
import { createActionCreatorInvariantMiddleware } from '@internal/actionCreatorInvariantMiddleware' | ||
import type { Dispatch, MiddlewareAPI } from '@reduxjs/toolkit' | ||
import { createAction } from '@reduxjs/toolkit' | ||
|
||
describe('createActionCreatorInvariantMiddleware', () => { | ||
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}) | ||
|
||
afterEach(() => { | ||
consoleSpy.mockClear() | ||
}) | ||
afterAll(() => { | ||
consoleSpy.mockRestore() | ||
}) | ||
|
||
const dummyAction = createAction('aSlice/anAction') | ||
|
||
it('sends the action through the middleware chain', () => { | ||
const next: Dispatch = (action) => ({ | ||
...action, | ||
returned: true, | ||
}) | ||
const dispatch = createActionCreatorInvariantMiddleware()( | ||
{} as MiddlewareAPI | ||
)(next) | ||
|
||
expect(dispatch(dummyAction())).toEqual({ | ||
...dummyAction(), | ||
returned: true, | ||
}) | ||
}) | ||
|
||
const makeActionTester = ( | ||
options?: ActionCreatorInvariantMiddlewareOptions | ||
) => | ||
createActionCreatorInvariantMiddleware(options)({} as MiddlewareAPI)( | ||
(action) => action | ||
) | ||
|
||
it('logs a warning to console if an action creator is mistakenly dispatched', () => { | ||
const testAction = makeActionTester() | ||
|
||
testAction(dummyAction()) | ||
|
||
expect(consoleSpy).not.toHaveBeenCalled() | ||
|
||
testAction(dummyAction) | ||
|
||
expect(consoleSpy).toHaveBeenLastCalledWith(getMessage(dummyAction.type)) | ||
}) | ||
|
||
it('allows passing a custom predicate', () => { | ||
let predicateCalled = false | ||
const testAction = makeActionTester({ | ||
isActionCreator(action): action is Function { | ||
predicateCalled = true | ||
return false | ||
}, | ||
}) | ||
testAction(dummyAction()) | ||
expect(predicateCalled).toBe(true) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.