diff --git a/docs/api/getDefaultMiddleware.md b/docs/api/getDefaultMiddleware.md index 28f6e4c6dd..ac51a106d3 100644 --- a/docs/api/getDefaultMiddleware.md +++ b/docs/api/getDefaultMiddleware.md @@ -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 diff --git a/docs/api/otherExports.md b/docs/api/otherExports.md index 80a84426cc..615f68d59f 100644 --- a/docs/api/otherExports.md +++ b/docs/api/otherExports.md @@ -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). diff --git a/package-lock.json b/package-lock.json index 09c1f57bdf..dc86414a6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1278,12 +1278,6 @@ "@types/node": "*" } }, - "@types/invariant": { - "version": "2.2.31", - "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.31.tgz", - "integrity": "sha512-jMlgg9pIURvy9jgBHCjQp/CyBjYHUwj91etVcDdXkFl2CwTFiQlB+8tcsMeXpXf2PFE5X2pjk4Gm43hQSMHAdA==", - "dev": true - }, "@types/istanbul-lib-coverage": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", @@ -1346,6 +1340,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/nanoid/-/nanoid-2.1.0.tgz", "integrity": "sha512-xdkn/oRTA0GSNPLIKZgHWqDTWZsVrieKomxJBOQUK9YDD+zfSgmwD5t4WJYra5S7XyhTw7tfvwznW+pFexaepQ==", + "dev": true, "requires": { "@types/node": "*" } @@ -1353,7 +1348,8 @@ "@types/node": { "version": "10.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.6.tgz", - "integrity": "sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg==" + "integrity": "sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg==", + "dev": true }, "@types/parse-json": { "version": "4.0.0", @@ -1361,15 +1357,6 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, - "@types/redux-immutable-state-invariant": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.1.tgz", - "integrity": "sha512-eosvcaKraeX8fyEKViFZ2vh3xSWF8teCSw+mEMjlwoN0Ai6ZAevczQr3YyZxZFfwH1LXUiV3aXcSAlsNKAnjNg==", - "dev": true, - "requires": { - "redux": "^3.6.0 || ^4.0.0" - } - }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -4689,6 +4676,7 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, "requires": { "loose-envify": "^1.0.0" } @@ -5726,7 +5714,8 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json5": { "version": "2.1.1", @@ -6934,15 +6923,6 @@ "symbol-observable": "^1.2.0" } }, - "redux-immutable-state-invariant": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz", - "integrity": "sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg==", - "requires": { - "invariant": "^2.1.0", - "json-stringify-safe": "^5.0.1" - } - }, "redux-thunk": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", diff --git a/package.json b/package.json index 08c452555f..751b97def3 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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" }, diff --git a/src/immutableStateInvariantMiddleware.ts b/src/immutableStateInvariantMiddleware.ts index d7fc58731e..dc21ab4642 100644 --- a/src/immutableStateInvariantMiddleware.ts +++ b/src/immutableStateInvariantMiddleware.ts @@ -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 ( @@ -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) @@ -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