Skip to content

Commit 247ae1a

Browse files
brandonrobertsMikeRyanDev
authored andcommitted
fix(StoreDevtools): Only recompute current state when reducers are updated (#570)
Closes #229, #487
1 parent 5a998ba commit 247ae1a

File tree

2 files changed

+79
-5
lines changed

2 files changed

+79
-5
lines changed

modules/store-devtools/spec/store.spec.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ describe('Store Devtools', () => {
259259
expect(getState()).toBe(2);
260260
});
261261

262-
it('should replace the reducer', () => {
262+
it('should replace the reducer and preserve previous states', () => {
263263
store.dispatch({ type: 'INCREMENT' });
264264
store.dispatch({ type: 'DECREMENT' });
265265
store.dispatch({ type: 'INCREMENT' });
@@ -268,7 +268,21 @@ describe('Store Devtools', () => {
268268

269269
fixture.replaceReducer(doubleCounter);
270270

271-
expect(getState()).toBe(2);
271+
expect(getState()).toBe(1);
272+
});
273+
274+
it('should replace the reducer and compute new state with latest reducer', () => {
275+
store.dispatch({ type: 'INCREMENT' });
276+
store.dispatch({ type: 'DECREMENT' });
277+
store.dispatch({ type: 'INCREMENT' });
278+
279+
expect(getState()).toBe(1);
280+
281+
fixture.replaceReducer(doubleCounter);
282+
283+
store.dispatch({ type: 'INCREMENT' });
284+
285+
expect(getState()).toBe(3);
272286
});
273287

274288
it('should catch and record errors', () => {
@@ -280,8 +294,8 @@ describe('Store Devtools', () => {
280294
store.dispatch({ type: 'INCREMENT' });
281295

282296
let { computedStates } = fixture.getLiftedState();
283-
expect(computedStates[2].error).toMatch(/ReferenceError/);
284-
expect(computedStates[3].error).toMatch(
297+
expect(computedStates[3].error).toMatch(/ReferenceError/);
298+
expect(computedStates[4].error).toMatch(
285299
/Interrupted by an error up the chain/
286300
);
287301

modules/store-devtools/src/reducer.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { difference, liftAction } from './utils';
1010
import * as Actions from './actions';
1111
import { StoreDevtoolsConfig } from './config';
12+
import { PerformAction } from './actions';
1213

1314
export type InitAction = {
1415
readonly type: typeof INIT;
@@ -300,7 +301,6 @@ export function liftReducerWith(
300301
} = liftedAction.nextLiftedState);
301302
break;
302303
}
303-
case UPDATE:
304304
case INIT: {
305305
// Always recompute states on hot reload and init.
306306
minInvalidatedStateIndex = 0;
@@ -325,6 +325,66 @@ export function liftReducerWith(
325325

326326
break;
327327
}
328+
case UPDATE: {
329+
const stateHasErrors =
330+
computedStates.filter(state => state.error).length > 0;
331+
332+
if (stateHasErrors) {
333+
// Recompute all states
334+
minInvalidatedStateIndex = 0;
335+
336+
if (options.maxAge && stagedActionIds.length > options.maxAge) {
337+
// States must be recomputed before committing excess.
338+
computedStates = recomputeStates(
339+
computedStates,
340+
minInvalidatedStateIndex,
341+
reducer,
342+
committedState,
343+
actionsById,
344+
stagedActionIds,
345+
skippedActionIds
346+
);
347+
348+
commitExcessActions(stagedActionIds.length - options.maxAge);
349+
350+
// Avoid double computation.
351+
minInvalidatedStateIndex = Infinity;
352+
}
353+
} else {
354+
if (currentStateIndex === stagedActionIds.length - 1) {
355+
currentStateIndex++;
356+
}
357+
358+
// Add a new action to only recompute state
359+
const actionId = nextActionId++;
360+
actionsById[actionId] = new PerformAction(liftedAction);
361+
stagedActionIds = [...stagedActionIds, actionId];
362+
363+
minInvalidatedStateIndex = stagedActionIds.length - 1;
364+
365+
// States must be recomputed before committing excess.
366+
computedStates = recomputeStates(
367+
computedStates,
368+
minInvalidatedStateIndex,
369+
reducer,
370+
committedState,
371+
actionsById,
372+
stagedActionIds,
373+
skippedActionIds
374+
);
375+
376+
currentStateIndex = minInvalidatedStateIndex;
377+
378+
if (options.maxAge && stagedActionIds.length > options.maxAge) {
379+
commitExcessActions(stagedActionIds.length - options.maxAge);
380+
}
381+
382+
// Avoid double computation.
383+
minInvalidatedStateIndex = Infinity;
384+
}
385+
386+
break;
387+
}
328388
default: {
329389
// If the action is not recognized, it's a monitor action.
330390
// Optimization: a monitor action can't change history.

0 commit comments

Comments
 (0)