Skip to content

Commit 4249b3a

Browse files
committed
Normalize actions in lifted state as per #134
1 parent 1540777 commit 4249b3a

File tree

5 files changed

+102
-76
lines changed

5 files changed

+102
-76
lines changed

examples/counter/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
},
1717
"homepage": "https://github.com/gaearon/redux-devtools#readme",
1818
"dependencies": {
19-
"react": "^0.14.0-rc1",
20-
"react-dom": "^0.14.0-rc1",
21-
"react-redux": "^3.0.0",
19+
"react": "^0.14.0",
20+
"react-dom": "^0.14.0",
21+
"react-redux": "^4.0.0",
2222
"redux": "^3.0.0",
2323
"redux-thunk": "^1.0.0"
2424
},
@@ -27,8 +27,8 @@
2727
"babel-loader": "^5.1.4",
2828
"node-libs-browser": "^0.5.2",
2929
"react-hot-loader": "^1.3.0",
30-
"redux-devtools": "^3.0.0-beta-1",
31-
"redux-devtools-log-monitor": "^1.0.0-beta-1",
30+
"redux-devtools": "^3.0.0-beta-2",
31+
"redux-devtools-log-monitor": "^1.0.0-beta-2",
3232
"redux-devtools-dock-monitor": "^1.0.0-beta-1",
3333
"webpack": "^1.9.11",
3434
"webpack-dev-server": "^1.9.0"

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "redux-devtools",
3-
"version": "3.0.0-beta-1",
3+
"version": "3.0.0-beta-2",
44
"description": "Redux DevTools with hot reloading and time travel",
55
"main": "lib/index.js",
66
"scripts": {
@@ -54,6 +54,7 @@
5454
"react": "^0.14.0"
5555
},
5656
"dependencies": {
57+
"lodash": "^3.10.1",
5758
"react-redux": "^4.0.0",
5859
"redux": "^3.0.0"
5960
}

src/instrument.js

Lines changed: 84 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import difference from 'lodash/array/difference';
2+
13
export const ActionTypes = {
24
PERFORM_ACTION: 'PERFORM_ACTION',
35
RESET: 'RESET',
@@ -33,8 +35,8 @@ export const ActionCreators = {
3335
return { type: ActionTypes.SWEEP };
3436
},
3537

36-
toggleAction(index) {
37-
return { type: ActionTypes.TOGGLE_ACTION, index };
38+
toggleAction(id) {
39+
return { type: ActionTypes.TOGGLE_ACTION, id };
3840
},
3941

4042
jumpToState(index) {
@@ -48,16 +50,6 @@ export const ActionCreators = {
4850

4951
const INIT_ACTION = { type: '@@INIT' };
5052

51-
function toggle(obj, key) {
52-
const clone = { ...obj };
53-
if (clone[key]) {
54-
delete clone[key];
55-
} else {
56-
clone[key] = true;
57-
}
58-
return clone;
59-
}
60-
6153
/**
6254
* Computes the next entry in the log by applying an action.
6355
*/
@@ -87,17 +79,17 @@ function computeNextEntry(reducer, action, state, error) {
8779
/**
8880
* Runs the reducer on all actions to get a fresh computation log.
8981
*/
90-
function recomputeStates(reducer, committedState, stagedActions, skippedActions) {
82+
function recomputeStates(reducer, committedState, actionsById, stagedActionIds, skippedActionIds) {
9183
const computedStates = [];
92-
93-
for (let i = 0; i < stagedActions.length; i++) {
94-
const action = stagedActions[i];
84+
for (let i = 0; i < stagedActionIds.length; i++) {
85+
const actionId = stagedActionIds[i];
86+
const action = actionsById[actionId].action;
9587

9688
const previousEntry = computedStates[i - 1];
9789
const previousState = previousEntry ? previousEntry.state : committedState;
9890
const previousError = previousEntry ? previousEntry.error : undefined;
9991

100-
const shouldSkip = Boolean(skippedActions[i]);
92+
const shouldSkip = skippedActionIds.indexOf(actionId) > -1;
10193
const entry = shouldSkip ?
10294
previousEntry :
10395
computeNextEntry(reducer, action, previousState, previousError);
@@ -108,17 +100,28 @@ function recomputeStates(reducer, committedState, stagedActions, skippedActions)
108100
return computedStates;
109101
}
110102

103+
/**
104+
* Lifts an app's action into an action on the lifted store.
105+
*/
106+
function liftAction(action) {
107+
return ActionCreators.performAction(action);
108+
}
109+
111110
/**
112111
* Creates a history state reducer from an app's reducer.
113112
*/
114113
function liftReducerWith(reducer, initialCommittedState, monitorReducer) {
115114
const initialLiftedState = {
115+
monitorState: monitorReducer(undefined, {}),
116+
nextActionId: 1,
117+
actionsById: {
118+
0: liftAction(INIT_ACTION)
119+
},
120+
stagedActionIds: [0],
121+
skippedActionIds: [],
116122
committedState: initialCommittedState,
117-
stagedActions: [INIT_ACTION],
118-
skippedActions: {},
119123
currentStateIndex: 0,
120-
timestamps: [Date.now()],
121-
monitorState: monitorReducer(undefined, {})
124+
computedStates: undefined
122125
};
123126

124127
/**
@@ -128,57 +131,79 @@ function liftReducerWith(reducer, initialCommittedState, monitorReducer) {
128131
let shouldRecomputeStates = true;
129132
let {
130133
monitorState,
134+
actionsById,
135+
nextActionId,
136+
stagedActionIds,
137+
skippedActionIds,
131138
committedState,
132-
stagedActions,
133-
skippedActions,
134-
computedStates,
135139
currentStateIndex,
136-
timestamps
140+
computedStates
137141
} = liftedState;
138142

139143
switch (liftedAction.type) {
140144
case ActionTypes.RESET:
145+
actionsById = {
146+
0: liftAction(INIT_ACTION)
147+
};
148+
nextActionId = 1;
149+
stagedActionIds = [0];
150+
skippedActionIds = [];
141151
committedState = initialCommittedState;
142-
stagedActions = [INIT_ACTION];
143-
skippedActions = {};
144152
currentStateIndex = 0;
145-
timestamps = [liftedAction.timestamp];
146153
break;
147154
case ActionTypes.COMMIT:
155+
actionsById = {
156+
0: liftAction(INIT_ACTION)
157+
};
158+
nextActionId = 1;
159+
stagedActionIds = [0];
160+
skippedActionIds = [];
148161
committedState = computedStates[currentStateIndex].state;
149-
stagedActions = [INIT_ACTION];
150-
skippedActions = {};
151162
currentStateIndex = 0;
152-
timestamps = [liftedAction.timestamp];
153163
break;
154164
case ActionTypes.ROLLBACK:
155-
stagedActions = [INIT_ACTION];
156-
skippedActions = {};
165+
actionsById = {
166+
0: liftAction(INIT_ACTION)
167+
};
168+
nextActionId = 1;
169+
stagedActionIds = [0];
170+
skippedActionIds = [];
157171
currentStateIndex = 0;
158-
timestamps = [liftedAction.timestamp];
159172
break;
160173
case ActionTypes.TOGGLE_ACTION:
161-
skippedActions = toggle(skippedActions, liftedAction.index);
174+
const index = skippedActionIds.indexOf(liftedAction.id);
175+
if (index === -1) {
176+
skippedActionIds = [
177+
liftedAction.id,
178+
...skippedActionIds
179+
];
180+
} else {
181+
skippedActionIds = [
182+
...skippedActionIds.slice(0, index),
183+
...skippedActionIds.slice(index + 1)
184+
];
185+
}
162186
break;
163187
case ActionTypes.JUMP_TO_STATE:
164188
currentStateIndex = liftedAction.index;
165189
// Optimization: we know the history has not changed.
166190
shouldRecomputeStates = false;
167191
break;
168192
case ActionTypes.SWEEP:
169-
stagedActions = stagedActions.filter((_, i) => !skippedActions[i]);
170-
timestamps = timestamps.filter((_, i) => !skippedActions[i]);
171-
skippedActions = {};
172-
currentStateIndex = Math.min(currentStateIndex, stagedActions.length - 1);
193+
stagedActionIds = difference(stagedActionIds, skippedActionIds);
194+
skippedActionIds = [];
195+
currentStateIndex = Math.min(currentStateIndex, stagedActionIds.length - 1);
173196
break;
174197
case ActionTypes.PERFORM_ACTION:
175-
if (currentStateIndex === stagedActions.length - 1) {
198+
if (currentStateIndex === stagedActionIds.length - 1) {
176199
currentStateIndex++;
177200
}
178201

179-
stagedActions = [...stagedActions, liftedAction.action];
180-
timestamps = [...timestamps, liftedAction.timestamp];
181-
202+
const actionId = nextActionId++;
203+
// Mutation! This is the hottest path, and we optimize on purpose.
204+
// It is safe because we set a new key in a cache dictionary.
205+
actionsById[actionId] = liftedAction;
206+
stagedActionIds = [...stagedActionIds, actionId];
182207
// Optimization: we know that the past has not changed.
183208
shouldRecomputeStates = false;
184209
// Instead of recomputing the states, append the next one.
@@ -193,12 +218,14 @@ function liftReducerWith(reducer, initialCommittedState, monitorReducer) {
193218
break;
194219
case ActionTypes.IMPORT_STATE:
195220
({
196-
stagedActions,
197-
skippedActions,
198-
computedStates,
221+
monitorState,
222+
actionsById,
223+
nextActionId,
224+
stagedActionIds,
225+
skippedActionIds,
226+
committedState,
199227
currentStateIndex,
200-
timestamps,
201-
monitorState
228+
computedStates
202229
} = liftedAction.nextLiftedState);
203230
break;
204231
case '@@redux/INIT':
@@ -215,21 +242,23 @@ function liftReducerWith(reducer, initialCommittedState, monitorReducer) {
215242
computedStates = recomputeStates(
216243
reducer,
217244
committedState,
218-
stagedActions,
219-
skippedActions
245+
actionsById,
246+
stagedActionIds,
247+
skippedActionIds
220248
);
221249
}
222250

223251
monitorState = monitorReducer(monitorState, liftedAction);
224252

225253
return {
254+
monitorState,
255+
actionsById,
256+
nextActionId,
257+
stagedActionIds,
258+
skippedActionIds,
226259
committedState,
227-
stagedActions,
228-
skippedActions,
229-
computedStates,
230260
currentStateIndex,
231-
timestamps,
232-
monitorState
261+
computedStates
233262
};
234263
};
235264
}
@@ -243,13 +272,6 @@ function unliftState(liftedState) {
243272
return state;
244273
}
245274

246-
/**
247-
* Lifts an app's action into an action on the lifted store.
248-
*/
249-
function liftAction(action) {
250-
return ActionCreators.performAction(action);
251-
}
252-
253275
/**
254276
* Provides an app's view into the lifted store.
255277
*/

src/persistState.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
const identity = _ => _;
1+
import mapValues from 'lodash/object/mapValues';
2+
import identity from 'lodash/utility/identity';
3+
24
export default function persistState(sessionId, deserializeState = identity, deserializeAction = identity) {
35
if (!sessionId) {
46
return next => (...args) => next(...args);
@@ -7,7 +9,10 @@ export default function persistState(sessionId, deserializeState = identity, des
79
function deserialize(state) {
810
return {
911
...state,
10-
stagedActions: state.stagedActions.map(deserializeAction),
12+
actionsById: mapValues(state.actionsById, liftedAction => ({
13+
...liftedAction,
14+
action: deserializeAction(liftedAction.action)
15+
})),
1116
committedState: deserializeState(state.committedState),
1217
computedStates: state.computedStates.map(computedState => ({
1318
...computedState,

test/instrument.spec.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('instrument', () => {
8787
});
8888

8989
it('should toggle an action', () => {
90-
// stateIndex 0 = @@INIT
90+
// actionId 0 = @@INIT
9191
store.dispatch({ type: 'INCREMENT' });
9292
store.dispatch({ type: 'DECREMENT' });
9393
store.dispatch({ type: 'INCREMENT' });
@@ -101,7 +101,7 @@ describe('instrument', () => {
101101
});
102102

103103
it('should sweep disabled actions', () => {
104-
// stateIndex 0 = @@INIT
104+
// actionId 0 = @@INIT
105105
store.dispatch({ type: 'INCREMENT' });
106106
store.dispatch({ type: 'DECREMENT' });
107107
store.dispatch({ type: 'INCREMENT' });
@@ -115,10 +115,8 @@ describe('instrument', () => {
115115
expect(store.getState()).toBe(3);
116116

117117
liftedStore.dispatch(ActionCreators.toggleAction(2));
118-
expect(store.getState()).toBe(2);
119-
120-
liftedStore.dispatch(ActionCreators.sweep());
121-
expect(store.getState()).toBe(2);
118+
// Now it has no effect because it's not staged
119+
expect(store.getState()).toBe(3);
122120
});
123121

124122
it('should jump to state', () => {

0 commit comments

Comments
 (0)