Skip to content

Commit a1d5e84

Browse files
authored
Immutable middleware cleanup (#385)
* Inline tiny-invariant and json-stringify-safe * Remove unused deps * Tweak immutable middleware docs typos
1 parent 6b83a39 commit a1d5e84

File tree

5 files changed

+79
-51
lines changed

5 files changed

+79
-51
lines changed

docs/api/getDefaultMiddleware.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ provide runtime checks for two common issues:
5858
- [`immutable-state-invariant`](./otherExports.md#createimmutablestateinvariantmiddleware): deeply compares
5959
state values for mutations. It can detect mutations in reducers during a dispatch, and also mutations that occur between
6060
dispatches (such as in a component or a selector). When a mutation is detected, it will throw an error and indicate the key
61-
path for where the mutated value was detected in the state tree.
62-
63-
Forked from [`redux-immutable-state-invariant`](https://github.com/leoasis/redux-immutable-state-invariant)
61+
path for where the mutated value was detected in the state tree. (Forked from [`redux-immutable-state-invariant`](https://github.com/leoasis/redux-immutable-state-invariant).)
6462

6563
- [`serializable-state-invariant-middleware`](./otherExports.md#createserializablestateinvariantmiddleware): a custom middleware created specifically for use in Redux Toolkit. Similar in
6664
concept to `immutable-state-invariant`, but deeply checks your state tree and your actions for non-serializable values

docs/api/otherExports.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Redux Toolkit exports some of its internal utilities, and re-exports additional
1111

1212
## Internal Exports
1313

14-
### `createImmutalStateInvariantMiddleware`
14+
### `createImmutableStateInvariantMiddleware`
1515

1616
Creates an instance of the `immutable-state-invariant` middleware described in [`getDefaultMiddleware`](./getDefaultMiddleware.md).
1717

package-lock.json

Lines changed: 6 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,10 @@
2424
"license": "MIT",
2525
"devDependencies": {
2626
"@microsoft/api-extractor": "^7.7.0",
27-
"@types/invariant": "^2.2.31",
2827
"@types/jest": "^24.0.11",
2928
"@types/json-stringify-safe": "^5.0.0",
29+
"@types/nanoid": "^2.1.0",
3030
"@types/node": "^10.14.4",
31-
"@types/redux-immutable-state-invariant": "^2.1.1",
3231
"console-testing-library": "^0.3.1",
3332
"eslint-config-react-app": "^5.0.1",
3433
"invariant": "^2.2.4",
@@ -58,11 +57,9 @@
5857
"src"
5958
],
6059
"dependencies": {
61-
"@types/nanoid": "^2.1.0",
6260
"immer": "^4.0.1",
6361
"nanoid": "^2.1.11",
6462
"redux": "^4.0.0",
65-
"redux-immutable-state-invariant": "^2.1.0",
6663
"redux-thunk": "^2.3.0",
6764
"reselect": "^4.0.0"
6865
},

src/immutableStateInvariantMiddleware.ts

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,64 @@
1-
import invariant from 'invariant'
2-
import stringify from 'json-stringify-safe'
31
import { Middleware } from 'redux'
42

5-
const BETWEEN_DISPATCHES_MESSAGE = [
6-
'A state mutation was detected between dispatches, in the path `%s`.',
7-
'This may cause incorrect behavior.',
8-
'(http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)'
9-
].join(' ')
3+
type EntryProcessor = (key: string, value: any) => any
104

11-
const INSIDE_DISPATCH_MESSAGE = [
12-
'A state mutation was detected inside a dispatch, in the path: `%s`.',
13-
'Take a look at the reducer(s) handling the action %s.',
14-
'(http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)'
15-
].join(' ')
5+
const isProduction: boolean = process.env.NODE_ENV === 'production'
6+
const prefix: string = 'Invariant failed'
7+
8+
// Throw an error if the condition fails
9+
// Strip out error messages for production
10+
// > Not providing an inline default argument for message as the result is smaller
11+
function invariant(condition: any, message?: string) {
12+
if (condition) {
13+
return
14+
}
15+
// Condition not passed
16+
17+
// In production we strip the message but still throw
18+
if (isProduction) {
19+
throw new Error(prefix)
20+
}
21+
22+
// When not in production we allow the message to pass through
23+
// *This block will be removed in production builds*
24+
throw new Error(`${prefix}: ${message || ''}`)
25+
}
26+
27+
function stringify(
28+
obj: any,
29+
serializer?: EntryProcessor,
30+
indent?: string | number,
31+
decycler?: EntryProcessor
32+
): string {
33+
return JSON.stringify(obj, getSerialize(serializer, decycler), indent)
34+
}
35+
36+
function getSerialize(
37+
serializer?: EntryProcessor,
38+
decycler?: EntryProcessor
39+
): EntryProcessor {
40+
let stack: any[] = [],
41+
keys: any[] = []
42+
43+
if (!decycler)
44+
decycler = function(_: string, value: any) {
45+
if (stack[0] === value) return '[Circular ~]'
46+
return (
47+
'[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'
48+
)
49+
}
50+
51+
return function(this: any, key: string, value: any) {
52+
if (stack.length > 0) {
53+
var thisPos = stack.indexOf(this)
54+
~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
55+
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
56+
if (~stack.indexOf(value)) value = decycler!.call(this, key, value)
57+
} else stack.push(value)
58+
59+
return serializer == null ? value : serializer.call(this, key, value)
60+
}
61+
}
1662

1763
export function isImmutableDefault(value: unknown): boolean {
1864
return (
@@ -150,8 +196,11 @@ export function createImmutableStateInvariantMiddleware(
150196

151197
invariant(
152198
!result.wasMutated,
153-
BETWEEN_DISPATCHES_MESSAGE,
154-
(result.path || []).join('.')
199+
`A state mutation was detected between dispatches, in the path '${(
200+
result.path || []
201+
).join(
202+
'.'
203+
)}'. This may cause incorrect behavior. (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)`
155204
)
156205

157206
const dispatchedAction = next(action)
@@ -164,9 +213,13 @@ export function createImmutableStateInvariantMiddleware(
164213
result.wasMutated &&
165214
invariant(
166215
!result.wasMutated,
167-
INSIDE_DISPATCH_MESSAGE,
168-
(result.path || []).join('.'),
169-
stringify(action)
216+
`A state mutation was detected inside a dispatch, in the path: ${(
217+
result.path || []
218+
).join(
219+
'.'
220+
)}. Take a look at the reducer(s) handling the action ${stringify(
221+
action
222+
)}. (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)`
170223
)
171224

172225
return dispatchedAction

0 commit comments

Comments
 (0)