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

Add ignoredPaths option to ignore serializability check #320

Merged
merged 5 commits into from
Jan 19, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
21 changes: 20 additions & 1 deletion docs/api/otherExports.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,26 @@ Redux Toolkit exports some of its internal utilities, and re-exports additional

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

Accepts an options object with `isSerializable` and `getEntries` parameters. The former, `isSerializable`, will be used to determine if a value is considered serializable or not. If not provided, this defaults to `isPlain`. The latter, `getEntries`, will be used to retrieve nested values. If not provided, `Object.entries` will be used by default.
Accepts a single configuration object parameter, with the following options:

```ts
function createSerializableStateInvariantMiddleware({
// 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[]
// An array of dot-separated path strings to ignore when checking for serializability.
// Defaults to []
ignoredPaths?: string[]
})
```

Example:

Expand Down
4 changes: 2 additions & 2 deletions etc/redux-toolkit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export interface EnhancedStore<S = any, A extends Action = AnyAction, M extends
}

// @public (undocumented)
export function findNonSerializableValue(value: unknown, path?: ReadonlyArray<string>, isSerializable?: (value: unknown) => boolean, getEntries?: (value: unknown) => [string, any][], ignoredSlices?: string[]): NonSerializableValue | false;
export function findNonSerializableValue(value: unknown, path?: ReadonlyArray<string>, isSerializable?: (value: unknown) => boolean, getEntries?: (value: unknown) => [string, any][], ignoredPaths?: string[]): NonSerializableValue | false;

// @public
export function getDefaultMiddleware<S = any, O extends Partial<GetDefaultMiddlewareOptions> = {
Expand Down Expand Up @@ -177,7 +177,7 @@ export type PrepareAction<P> = ((...args: any[]) => {
export interface SerializableStateInvariantMiddlewareOptions {
getEntries?: (value: any) => [string, any][];
ignoredActions?: string[];
ignoredSlices?: string[];
ignoredPaths?: string[];
isSerializable?: (value: any) => boolean;
}

Expand Down
26 changes: 21 additions & 5 deletions src/serializableStateInvariantMiddleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,6 @@ describe('serializableStateInvariantMiddleware', () => {

it('should not check serializability for ignored slice names', () => {
const ACTION_TYPE = 'TEST_ACTION'
const SLICE_NAME = 'testSlice'

const initialState = {
a: 0
Expand All @@ -344,7 +343,12 @@ describe('serializableStateInvariantMiddleware', () => {
switch (action.type) {
case ACTION_TYPE: {
return {
a: badValue
a: badValue,
b: {
c: badValue,
d: badValue
},
e: { f: badValue }
}
}
default:
Expand All @@ -354,19 +358,31 @@ describe('serializableStateInvariantMiddleware', () => {

const serializableStateInvariantMiddleware = createSerializableStateInvariantMiddleware(
{
ignoredSlices: [SLICE_NAME]
ignoredPaths: [
// Test for ignoring a single value
'testSlice.a',
// Test for ignoring a single nested value
'testSlice.b.c',
// Test for ignoring an object and its children
'testSlice.e'
]
}
)

const store = configureStore({
reducer: {
[SLICE_NAME]: reducer
testSlice: reducer
},
middleware: [serializableStateInvariantMiddleware]
})

store.dispatch({ type: ACTION_TYPE })

expect(getLog().log).toBe('')
// testSlice.b.d was not covered in ignoredPaths, so will still log the error
expect(getLog().log).toMatchInlineSnapshot(`
"A non-serializable value was detected in the state, in the path: \`testSlice.b.d\`. Value: Map {}
Take a look at the reducer(s) handling this action type: TEST_ACTION.
(See https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state)"
`)
})
})
20 changes: 10 additions & 10 deletions src/serializableStateInvariantMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,10 @@ export function findNonSerializableValue(
path: ReadonlyArray<string> = [],
isSerializable: (value: unknown) => boolean = isPlain,
getEntries?: (value: unknown) => [string, any][],
ignoredSlices: string[] = []
ignoredPaths: string[] = []
): NonSerializableValue | false {
let foundNestedSerializable: NonSerializableValue | false

if (ignoredSlices.includes(path[0])) {
return false
}

if (!isSerializable(value)) {
return {
keyPath: path.join('.') || '<root>',
Expand All @@ -59,6 +55,10 @@ export function findNonSerializableValue(
for (const [property, nestedValue] of entries) {
const nestedPath = path.concat(property)

if (ignoredPaths.indexOf(nestedPath.join('.')) >= 0) {
kevin940726 marked this conversation as resolved.
Show resolved Hide resolved
continue
}

if (!isSerializable(nestedValue)) {
return {
keyPath: nestedPath.join('.'),
Expand All @@ -72,7 +72,7 @@ export function findNonSerializableValue(
nestedPath,
isSerializable,
getEntries,
ignoredSlices
ignoredPaths
)

if (foundNestedSerializable) {
Expand Down Expand Up @@ -109,9 +109,9 @@ export interface SerializableStateInvariantMiddlewareOptions {
ignoredActions?: string[]

/**
* An array of slice names to ignore when checking for serializability, Defaults to []
* An array of dot-separated path strings to ignore when checking for serializability, Defaults to []
*/
ignoredSlices?: string[]
ignoredPaths?: string[]
}

/**
Expand All @@ -130,7 +130,7 @@ export function createSerializableStateInvariantMiddleware(
isSerializable = isPlain,
getEntries,
ignoredActions = [],
ignoredSlices = []
ignoredPaths = []
} = options

return storeAPI => next => action => {
Expand Down Expand Up @@ -166,7 +166,7 @@ export function createSerializableStateInvariantMiddleware(
[],
isSerializable,
getEntries,
ignoredSlices
ignoredPaths
)

if (foundStateNonSerializableValue) {
Expand Down