Skip to content

Commit eff7527

Browse files
authored
Merge pull request #2000 from reduxjs/fix-serializableCheck
2 parents 563f705 + 0dea2e7 commit eff7527

File tree

2 files changed

+75
-31
lines changed

2 files changed

+75
-31
lines changed

packages/toolkit/src/serializableStateInvariantMiddleware.ts

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -169,41 +169,40 @@ export function createSerializableStateInvariantMiddleware(
169169
} = options
170170

171171
return (storeAPI) => (next) => (action) => {
172-
if (
173-
ignoreActions ||
174-
(ignoredActions.length && ignoredActions.indexOf(action.type) !== -1)
175-
) {
176-
return next(action)
177-
}
172+
const result = next(action)
178173

179174
const measureUtils = getTimeMeasureUtils(
180175
warnAfter,
181176
'SerializableStateInvariantMiddleware'
182177
)
183-
measureUtils.measureTime(() => {
184-
const foundActionNonSerializableValue = findNonSerializableValue(
185-
action,
186-
'',
187-
isSerializable,
188-
getEntries,
189-
ignoredActionPaths
190-
)
191-
192-
if (foundActionNonSerializableValue) {
193-
const { keyPath, value } = foundActionNonSerializableValue
194178

195-
console.error(
196-
`A non-serializable value was detected in an action, in the path: \`${keyPath}\`. Value:`,
197-
value,
198-
'\nTake a look at the logic that dispatched this action: ',
179+
if (
180+
!ignoreActions &&
181+
!(ignoredActions.length && ignoredActions.indexOf(action.type) !== -1)
182+
) {
183+
measureUtils.measureTime(() => {
184+
const foundActionNonSerializableValue = findNonSerializableValue(
199185
action,
200-
'\n(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)',
201-
'\n(To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)'
186+
'',
187+
isSerializable,
188+
getEntries,
189+
ignoredActionPaths
202190
)
203-
}
204-
})
205191

206-
const result = next(action)
192+
if (foundActionNonSerializableValue) {
193+
const { keyPath, value } = foundActionNonSerializableValue
194+
195+
console.error(
196+
`A non-serializable value was detected in an action, in the path: \`${keyPath}\`. Value:`,
197+
value,
198+
'\nTake a look at the logic that dispatched this action: ',
199+
action,
200+
'\n(See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants)',
201+
'\n(To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)'
202+
)
203+
}
204+
})
205+
}
207206

208207
if (!ignoreState) {
209208
measureUtils.measureTime(() => {

packages/toolkit/src/tests/serializableStateInvariantMiddleware.test.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,13 @@ describe('serializableStateInvariantMiddleware', () => {
326326

327327
store.dispatch({ type: 'IGNORE_ME' })
328328

329-
expect(numTimesCalled).toBe(0)
329+
// The state check only calls `isSerializable` once
330+
expect(numTimesCalled).toBe(1)
330331

331332
store.dispatch({ type: 'ANY_OTHER_ACTION' })
332333

333-
expect(numTimesCalled).toBeGreaterThan(0)
334+
// Action checks call `isSerializable` 2+ times when enabled
335+
expect(numTimesCalled).toBeGreaterThanOrEqual(3)
334336
})
335337

336338
describe('ignored action paths', () => {
@@ -410,7 +412,12 @@ describe('serializableStateInvariantMiddleware', () => {
410412

411413
store.dispatch({ type: 'THIS_DOESNT_MATTER' })
412414

413-
expect(numTimesCalled).toBe(0)
415+
// `isSerializable` is called once for a state check
416+
expect(numTimesCalled).toBe(1)
417+
418+
store.dispatch({ type: 'THIS_DOESNT_MATTER_AGAIN' })
419+
420+
expect(numTimesCalled).toBe(2)
414421
})
415422

416423
it('should not check serializability for ignored slice names', () => {
@@ -470,17 +477,55 @@ describe('serializableStateInvariantMiddleware', () => {
470477

471478
it('allows ignoring state entirely', () => {
472479
const badValue = new Map()
480+
let numTimesCalled = 0
473481
const reducer = () => badValue
474-
configureStore({
482+
const store = configureStore({
475483
reducer,
476484
middleware: [
477485
createSerializableStateInvariantMiddleware({
486+
isSerializable: () => {
487+
numTimesCalled++
488+
return true
489+
},
478490
ignoreState: true,
479491
}),
480492
],
481-
}).dispatch({ type: 'test' })
493+
})
494+
495+
expect(numTimesCalled).toBe(0)
496+
497+
store.dispatch({ type: 'test' })
482498

483499
expect(getLog().log).toMatchInlineSnapshot(`""`)
500+
501+
// Should be called twice for the action - there is an initial check for early returns, then a second and potentially 3rd for nested properties
502+
expect(numTimesCalled).toBe(2)
503+
})
504+
505+
it('never calls isSerializable if both ignoreState and ignoreActions are true', () => {
506+
const badValue = new Map()
507+
let numTimesCalled = 0
508+
const reducer = () => badValue
509+
const store = configureStore({
510+
reducer,
511+
middleware: [
512+
createSerializableStateInvariantMiddleware({
513+
isSerializable: () => {
514+
numTimesCalled++
515+
return true
516+
},
517+
ignoreState: true,
518+
ignoreActions: true,
519+
}),
520+
],
521+
})
522+
523+
expect(numTimesCalled).toBe(0)
524+
525+
store.dispatch({ type: 'TEST', payload: new Date() })
526+
store.dispatch({ type: 'OTHER_THING' })
527+
528+
expect(numTimesCalled).toBe(0)
484529
})
485530

486531
it('Should print a warning if execution takes too long', () => {

0 commit comments

Comments
 (0)