UNPKG

@reduxjs/toolkit

Version:

The official, opinionated, batteries-included toolset for efficient Redux development

161 lines (134 loc) 4.66 kB
import isPlainObject from './isPlainObject' import { Middleware } from 'redux' /** * Returns true if the passed value is "plain", i.e. a value that is either * directly JSON-serializable (boolean, number, string, array, plain object) * or `undefined`. * * @param val The value to check. */ export function isPlain(val: any) { return ( typeof val === 'undefined' || val === null || typeof val === 'string' || typeof val === 'boolean' || typeof val === 'number' || Array.isArray(val) || isPlainObject(val) ) } const NON_SERIALIZABLE_STATE_MESSAGE = [ 'A non-serializable value was detected in the state, in the path: `%s`. Value: %o', 'Take a look at the reducer(s) handling this action type: %s.', '(See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)' ].join('\n') const NON_SERIALIZABLE_ACTION_MESSAGE = [ 'A non-serializable value was detected in an action, in the path: `%s`. Value: %o', 'Take a look at the logic that dispatched this action: %o.', '(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)' ].join('\n') interface NonSerializableValue { keyPath: string value: unknown } export function findNonSerializableValue( value: unknown, path: ReadonlyArray<string> = [], isSerializable: (value: unknown) => boolean = isPlain, getEntries?: (value: unknown) => [string, any][] ): NonSerializableValue | false { let foundNestedSerializable: NonSerializableValue | false if (!isSerializable(value)) { return { keyPath: path.join('.') || '<root>', value: value } } if (typeof value !== 'object' || value === null) { return false } const entries = getEntries != null ? getEntries(value) : Object.entries(value) for (const [property, nestedValue] of entries) { const nestedPath = path.concat(property) if (!isSerializable(nestedValue)) { return { keyPath: nestedPath.join('.'), value: nestedValue } } if (typeof nestedValue === 'object') { foundNestedSerializable = findNonSerializableValue( nestedValue, nestedPath, isSerializable, getEntries ) if (foundNestedSerializable) { return foundNestedSerializable } } } return false } /** * Options for `createSerializableStateInvariantMiddleware()`. */ export interface SerializableStateInvariantMiddlewareOptions { /** * The function to check if a value is considered serializable. This * function is applied recursively to every value contained in the * state. Defaults to `isPlain()`. */ isSerializable?: (value: any) => boolean /** * The function that will be used to retrieve entries from each * value. If unspecified, `Object.entries` will be used. Defaults * to `undefined`. */ getEntries?: (value: any) => [string, any][] /** * An array of action types to ignore when checking for serializability, Defaults to [] */ ignoredActions?: string[] } /** * Creates a middleware that, after every state change, checks if the new * state is serializable. If a non-serializable value is found within the * state, an error is printed to the console. * * @param options Middleware options. */ export function createSerializableStateInvariantMiddleware( options: SerializableStateInvariantMiddlewareOptions = {} ): Middleware { const { isSerializable = isPlain, getEntries, ignoredActions = [] } = options return storeAPI => next => action => { if (ignoredActions.length && ignoredActions.indexOf(action.type) !== -1) { return next(action) } const foundActionNonSerializableValue = findNonSerializableValue( action, [], isSerializable, getEntries ) if (foundActionNonSerializableValue) { const { keyPath, value } = foundActionNonSerializableValue console.error(NON_SERIALIZABLE_ACTION_MESSAGE, keyPath, value, action) } const result = next(action) const state = storeAPI.getState() const foundStateNonSerializableValue = findNonSerializableValue( state, [], isSerializable, getEntries ) if (foundStateNonSerializableValue) { const { keyPath, value } = foundStateNonSerializableValue console.error(NON_SERIALIZABLE_STATE_MESSAGE, keyPath, value, action.type) } return result } }