Skip to content

Commit b475ca3

Browse files
committed
Merge branch 'v2.0-integration' into get-default-enhancers
2 parents 61b6240 + 6277952 commit b475ca3

19 files changed

+361
-43
lines changed

docs/api/actionCreatorMiddleware.mdx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
id: actionCreatorMiddleware
3+
title: Action Creator Middleware
4+
sidebar_label: Action Creator Middleware
5+
hide_title: true
6+
---
7+
8+
 
9+
10+
# Action Creator Middleware
11+
12+
A custom middleware that detects if an action creator has been mistakenly dispatched, instead of being called before dispatching.
13+
14+
A common mistake is to call `dispatch(actionCreator)` instead of `dispatch(actionCreator())`.
15+
This tends to "work" as the action creator has the static `type` property, but can lead to unexpected behaviour.
16+
17+
## Options
18+
19+
```ts no-transpile
20+
export interface ActionCreatorInvariantMiddlewareOptions {
21+
/**
22+
* The function to identify whether a value is an action creator.
23+
* The default checks for a function with a static type property and match method.
24+
*/
25+
isActionCreator?: (action: unknown) => action is Function & { type?: unknown }
26+
}
27+
```
28+
29+
## Exports
30+
31+
### `createActionCreatorInvariantMiddleware`
32+
33+
Creates an instance of the action creator check middleware, with the given options.
34+
35+
You will most likely not need to call this yourself, as `getDefaultMiddleware` already does so.
36+
Example:
37+
38+
```ts
39+
// file: reducer.ts noEmit
40+
41+
export default function (state = {}, action: any) {
42+
return state
43+
}
44+
45+
// file: store.ts
46+
47+
import {
48+
configureStore,
49+
createActionCreatorInvariantMiddleware,
50+
} from '@reduxjs/toolkit'
51+
import reducer from './reducer'
52+
53+
// Augment middleware to consider all functions with a static type property to be action creators
54+
const isActionCreator = (
55+
action: unknown
56+
): action is Function & { type: unknown } =>
57+
typeof action === 'function' && 'type' in action
58+
59+
const actionCreatorMiddleware = createActionCreatorInvariantMiddleware({
60+
isActionCreator,
61+
})
62+
63+
const store = configureStore({
64+
reducer,
65+
middleware: [actionCreatorMiddleware],
66+
})
67+
```

docs/api/autoBatchEnhancer.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ Any action that is tagged with `action.meta[SHOULD_AUTOBATCH] = true` will be tr
8484
`autoBatchEnhancer` accepts options to configure how the notification callback is queued:
8585
8686
- `{type: 'raf'}`: queues using `requestAnimationFrame` (default)
87-
- `{type: 'tick'}: queues using `queueMicrotask`
87+
- `{type: 'tick'}`: queues using `queueMicrotask`
8888
- `{type: 'timer, timeout: number}`: queues using `setTimeout`
89-
- `{type: 'callback', queueNotification: (notify: () => void) => void}: lets you provide your own callback, such as a debounced or throttled function
89+
- `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback, such as a debounced or throttled function
9090

9191
The default behavior is to queue the notifications using `requestAnimationFrame`.
9292

docs/api/getDefaultMiddleware.mdx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ It is preferable to use the chainable `.concat(...)` and `.prepend(...)` methods
7070

7171
One of the goals of Redux Toolkit is to provide opinionated defaults and prevent common mistakes. As part of that,
7272
`getDefaultMiddleware` includes some middleware that are added **in development builds of your app only** to
73-
provide runtime checks for two common issues:
73+
provide runtime checks for three common issues:
7474

7575
- [Immutability check middleware](./immutabilityMiddleware.mdx): deeply compares
7676
state values for mutations. It can detect mutations in reducers during a dispatch, and also mutations that occur between
@@ -82,13 +82,21 @@ provide runtime checks for two common issues:
8282
such as functions, Promises, Symbols, and other non-plain-JS-data values. When a non-serializable value is detected, a
8383
console error will be printed with the key path for where the non-serializable value was detected.
8484

85+
- [Action creator check middleware](./actionCreatorMiddleware.mdx): another custom middleware created specifically for use in Redux Toolkit.
86+
Identifies when an action creator was mistakenly dispatched without being called, and warns to console with the action type.
87+
8588
In addition to these development tool middleware, it also adds [`redux-thunk`](https://github.com/reduxjs/redux-thunk)
8689
by default, since thunks are the basic recommended side effects middleware for Redux.
8790

8891
Currently, the return value is:
8992

9093
```js
91-
const middleware = [thunk, immutableStateInvariant, serializableStateInvariant]
94+
const middleware = [
95+
actionCreatorInvariant,
96+
immutableStateInvariant,
97+
thunk,
98+
serializableStateInvariant,
99+
]
92100
```
93101

94102
### Production
@@ -153,10 +161,15 @@ interface SerializableStateInvariantMiddlewareOptions {
153161
// See "Serializability Middleware" page for definition
154162
}
155163

164+
interface ActionCreatorInvariantMiddlewareOptions {
165+
// See "Action Creator Middleware" page for definition
166+
}
167+
156168
interface GetDefaultMiddlewareOptions {
157169
thunk?: boolean | ThunkOptions
158170
immutableCheck?: boolean | ImmutableStateInvariantMiddlewareOptions
159171
serializableCheck?: boolean | SerializableStateInvariantMiddlewareOptions
172+
actionCreatorCheck?: boolean | ActionCreatorInvariantMiddlewareOptions
160173
}
161174

162175
function getDefaultMiddleware<S = any>(

docs/introduction/getting-started.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,26 @@ you make your Redux code better.
3232

3333
## Installation
3434

35-
### Using Create React App
35+
### Create a React Redux App
3636

37-
The recommended way to start new apps with React and Redux is by using the [official Redux+JS template](https://github.com/reduxjs/cra-template-redux) or [Redux+TS template](https://github.com/reduxjs/cra-template-redux-typescript) for [Create React App](https://github.com/facebook/create-react-app), which takes advantage of **[Redux Toolkit](https://redux-toolkit.js.org/)** and React Redux's integration with React components.
37+
The recommended way to start new apps with React and Redux is by using [our official Redux+TS template for Vite](https://github.com/reduxjs/redux-templates), or by creating a new Next.js project using [Next's `with-redux` template](https://github.com/vercel/next.js/tree/canary/examples/with-redux).
38+
39+
Both of these already have Redux Toolkit and React-Redux configured appropriately for that build tool, and come with a small example app that demonstrates how to use several of Redux Toolkit's features.
3840

3941
```bash
40-
# Redux + Plain JS template
41-
npx create-react-app my-app --template redux
42+
# Vite with our Redux+TS template
43+
# (using the `degit` tool to clone and extract the template)
44+
npx degit reduxjs/redux-templates/packages/vite-template-redux my-app
4245

43-
# Redux + TypeScript template
44-
npx create-react-app my-app --template redux-typescript
46+
# Next.js using the `with-redux` template
47+
npx create-next-app --example with-redux my-app
4548
```
4649

50+
We do not currently have official React Native templates, but recommend these templates for standard React Native and for Expo:
51+
52+
- https://github.com/rahsheen/react-native-template-redux-typescript
53+
- https://github.com/rahsheen/expo-template-redux-typescript
54+
4755
### An Existing App
4856

4957
Redux Toolkit is available as a package on NPM for use with a module bundler or in a Node application:

docs/rtk-query/comparison.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,6 @@ This comparison table strives to be as accurate and as unbiased as possible. If
9595

9696
## Further Information
9797

98-
- The [React Query "Comparison" page](https://react-query.tanstack.com/comparison) has an additional detailed feature set comparison table and discussion of capabilities
98+
- The [React Query "Comparison" page](https://tanstack.com/query/latest/docs/react/comparison) has an additional detailed feature set comparison table and discussion of capabilities
9999
- Urql maintainer Phil Pluckthun wrote [an excellent explanation of what a "normalized cache" is and how Urql's cache works](https://kitten.sh/graphql-normalized-caching)
100100
- The [RTK Query "Cache Behavior" page](./usage/cache-behavior.mdx#tradeoffs) has further details on why RTK Query does not implement a normalized cache

examples/query/react/mutations/src/mocks/db.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ export const handlers = [
5151
const id = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id
5252

5353
const post = db.post.update({
54-
where: { id },
54+
where: {
55+
id: {
56+
equals: id,
57+
},
58+
},
5559
data: { name },
5660
})
5761

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Middleware } from 'redux'
2+
import { isActionCreator as isRTKAction } from './createAction'
3+
4+
export interface ActionCreatorInvariantMiddlewareOptions {
5+
/**
6+
* The function to identify whether a value is an action creator.
7+
* The default checks for a function with a static type property and match method.
8+
*/
9+
isActionCreator?: (action: unknown) => action is Function & { type?: unknown }
10+
}
11+
12+
export function getMessage(type?: unknown) {
13+
const splitType = type ? `${type}`.split('/') : []
14+
const actionName = splitType[splitType.length - 1] || 'actionCreator'
15+
return `Detected an action creator with type "${
16+
type || 'unknown'
17+
}" being dispatched.
18+
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.`
19+
}
20+
21+
export function createActionCreatorInvariantMiddleware(
22+
options: ActionCreatorInvariantMiddlewareOptions = {}
23+
): Middleware {
24+
if (process.env.NODE_ENV === 'production') {
25+
return () => (next) => (action) => next(action)
26+
}
27+
const { isActionCreator = isRTKAction } = options
28+
return () => (next) => (action) => {
29+
if (isActionCreator(action)) {
30+
console.warn(getMessage(action.type))
31+
}
32+
return next(action)
33+
}
34+
}

packages/toolkit/src/createAction.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
IfVoid,
66
IsAny,
77
} from './tsHelpers'
8+
import { hasMatchFunction } from './tsHelpers'
89
import isPlainObject from './isPlainObject'
910

1011
/**
@@ -293,6 +294,20 @@ export function isAction(action: unknown): action is Action<unknown> {
293294
return isPlainObject(action) && 'type' in action
294295
}
295296

297+
/**
298+
* Returns true if value is an RTK-like action creator, with a static type property and match method.
299+
*/
300+
export function isActionCreator(
301+
action: unknown
302+
): action is BaseActionCreator<unknown, string> & Function {
303+
return (
304+
typeof action === 'function' &&
305+
'type' in action &&
306+
// hasMatchFunction only wants Matchers but I don't see the point in rewriting it
307+
hasMatchFunction(action as any)
308+
)
309+
}
310+
296311
/**
297312
* Returns true if value is an action with a string type and valid Flux Standard Action keys.
298313
*/

packages/toolkit/src/createAsyncThunk.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type BaseThunkAPI<
1515
S,
1616
E,
1717
D extends Dispatch = Dispatch,
18-
RejectedValue = undefined,
18+
RejectedValue = unknown,
1919
RejectedMeta = unknown,
2020
FulfilledMeta = unknown
2121
> = {
@@ -137,7 +137,7 @@ type GetDispatch<ThunkApiConfig> = ThunkApiConfig extends {
137137
>
138138
: ThunkDispatch<GetState<ThunkApiConfig>, GetExtra<ThunkApiConfig>, AnyAction>
139139

140-
type GetThunkAPI<ThunkApiConfig> = BaseThunkAPI<
140+
export type GetThunkAPI<ThunkApiConfig> = BaseThunkAPI<
141141
GetState<ThunkApiConfig>,
142142
GetExtra<ThunkApiConfig>,
143143
GetDispatch<ThunkApiConfig>,

packages/toolkit/src/getDefaultMiddleware.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { Middleware, AnyAction } from 'redux'
22
import type { ThunkMiddleware } from 'redux-thunk'
33
import { thunk as thunkMiddleware, withExtraArgument } from 'redux-thunk'
4+
import type { ActionCreatorInvariantMiddlewareOptions } from './actionCreatorInvariantMiddleware'
5+
import { createActionCreatorInvariantMiddleware } from './actionCreatorInvariantMiddleware'
46
import type { ImmutableStateInvariantMiddlewareOptions } from './immutableStateInvariantMiddleware'
57
/* PROD_START_REMOVE_UMD */
68
import { createImmutableStateInvariantMiddleware } from './immutableStateInvariantMiddleware'
@@ -23,6 +25,7 @@ interface GetDefaultMiddlewareOptions {
2325
thunk?: boolean | ThunkOptions
2426
immutableCheck?: boolean | ImmutableStateInvariantMiddlewareOptions
2527
serializableCheck?: boolean | SerializableStateInvariantMiddlewareOptions
28+
actionCreatorCheck?: boolean | ActionCreatorInvariantMiddlewareOptions
2629
}
2730

2831
export type ThunkMiddlewareFor<
@@ -41,6 +44,7 @@ export type GetDefaultMiddleware<S = any> = <
4144
thunk: true
4245
immutableCheck: true
4346
serializableCheck: true
47+
actionCreatorCheck: true
4448
}
4549
>(
4650
options?: O
@@ -52,6 +56,7 @@ export const buildGetDefaultMiddleware = <S = any>(): GetDefaultMiddleware<S> =>
5256
thunk = true,
5357
immutableCheck = true,
5458
serializableCheck = true,
59+
actionCreatorCheck = true,
5560
} = options ?? {}
5661

5762
let middlewareArray = new MiddlewareArray<Middleware[]>()
@@ -92,6 +97,17 @@ export const buildGetDefaultMiddleware = <S = any>(): GetDefaultMiddleware<S> =>
9297
)
9398
}
9499
}
100+
if (actionCreatorCheck) {
101+
let actionCreatorOptions: ActionCreatorInvariantMiddlewareOptions = {}
102+
103+
if (!isBoolean(actionCreatorCheck)) {
104+
actionCreatorOptions = actionCreatorCheck
105+
}
106+
107+
middlewareArray.unshift(
108+
createActionCreatorInvariantMiddleware(actionCreatorOptions)
109+
)
110+
}
95111

96112
return middlewareArray as any
97113
}

0 commit comments

Comments
 (0)