Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immutable middleware cleanup #385

Merged
merged 3 commits into from
Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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