Skip to content

Commit

Permalink
Immutable middleware cleanup (#385)
Browse files Browse the repository at this point in the history
* Inline tiny-invariant and json-stringify-safe

* Remove unused deps

* Tweak immutable middleware docs typos
  • Loading branch information
markerikson authored Feb 21, 2020
1 parent 6b83a39 commit a1d5e84
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 51 deletions.
4 changes: 1 addition & 3 deletions docs/api/getDefaultMiddleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ provide runtime checks for two common issues:
- [`immutable-state-invariant`](./otherExports.md#createimmutablestateinvariantmiddleware): deeply compares
state values for mutations. It can detect mutations in reducers during a dispatch, and also mutations that occur between
dispatches (such as in a component or a selector). When a mutation is detected, it will throw an error and indicate the key
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)
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).)

- [`serializable-state-invariant-middleware`](./otherExports.md#createserializablestateinvariantmiddleware): a custom middleware created specifically for use in Redux Toolkit. Similar in
concept to `immutable-state-invariant`, but deeply checks your state tree and your actions for non-serializable values
Expand Down
2 changes: 1 addition & 1 deletion docs/api/otherExports.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Redux Toolkit exports some of its internal utilities, and re-exports additional

## Internal Exports

### `createImmutalStateInvariantMiddleware`
### `createImmutableStateInvariantMiddleware`

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

Expand Down
32 changes: 6 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@
"license": "MIT",
"devDependencies": {
"@microsoft/api-extractor": "^7.7.0",
"@types/invariant": "^2.2.31",
"@types/jest": "^24.0.11",
"@types/json-stringify-safe": "^5.0.0",
"@types/nanoid": "^2.1.0",
"@types/node": "^10.14.4",
"@types/redux-immutable-state-invariant": "^2.1.1",
"console-testing-library": "^0.3.1",
"eslint-config-react-app": "^5.0.1",
"invariant": "^2.2.4",
Expand Down Expand Up @@ -58,11 +57,9 @@
"src"
],
"dependencies": {
"@types/nanoid": "^2.1.0",
"immer": "^4.0.1",
"nanoid": "^2.1.11",
"redux": "^4.0.0",
"redux-immutable-state-invariant": "^2.1.0",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0"
},
Expand Down
87 changes: 70 additions & 17 deletions src/immutableStateInvariantMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,64 @@
import invariant from 'invariant'
import stringify from 'json-stringify-safe'
import { Middleware } from 'redux'

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

const INSIDE_DISPATCH_MESSAGE = [
'A state mutation was detected inside a dispatch, in the path: `%s`.',
'Take a look at the reducer(s) handling the action %s.',
'(http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)'
].join(' ')
const isProduction: boolean = process.env.NODE_ENV === 'production'
const prefix: string = 'Invariant failed'

// Throw an error if the condition fails
// Strip out error messages for production
// > Not providing an inline default argument for message as the result is smaller
function invariant(condition: any, message?: string) {
if (condition) {
return
}
// Condition not passed

// In production we strip the message but still throw
if (isProduction) {
throw new Error(prefix)
}

// When not in production we allow the message to pass through
// *This block will be removed in production builds*
throw new Error(`${prefix}: ${message || ''}`)
}

function stringify(
obj: any,
serializer?: EntryProcessor,
indent?: string | number,
decycler?: EntryProcessor
): string {
return JSON.stringify(obj, getSerialize(serializer, decycler), indent)
}

function getSerialize(
serializer?: EntryProcessor,
decycler?: EntryProcessor
): EntryProcessor {
let stack: any[] = [],
keys: any[] = []

if (!decycler)
decycler = function(_: string, value: any) {
if (stack[0] === value) return '[Circular ~]'
return (
'[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'
)
}

return function(this: any, key: string, value: any) {
if (stack.length > 0) {
var thisPos = stack.indexOf(this)
~thisPos ? stack.splice(thisPos + 1) : stack.push(this)
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key)
if (~stack.indexOf(value)) value = decycler!.call(this, key, value)
} else stack.push(value)

return serializer == null ? value : serializer.call(this, key, value)
}
}

export function isImmutableDefault(value: unknown): boolean {
return (
Expand Down Expand Up @@ -150,8 +196,11 @@ export function createImmutableStateInvariantMiddleware(

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

const dispatchedAction = next(action)
Expand All @@ -164,9 +213,13 @@ export function createImmutableStateInvariantMiddleware(
result.wasMutated &&
invariant(
!result.wasMutated,
INSIDE_DISPATCH_MESSAGE,
(result.path || []).join('.'),
stringify(action)
`A state mutation was detected inside a dispatch, in the path: ${(
result.path || []
).join(
'.'
)}. Take a look at the reducer(s) handling the action ${stringify(
action
)}. (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)`
)

return dispatchedAction
Expand Down

0 comments on commit a1d5e84

Please sign in to comment.