Commit 96cc2de8 authored by Robert Knight's avatar Robert Knight

Freeze store state with `immutable` in development builds

Deep-freeze the store state after each action in development builds to
catch accidental mutation bugs such as #1879.

We couldn't do this in the past because the `annotation` component used
to mutate newly created annotations. As a result of recent refactoring,
this no longer happens.
parent 0c9eb7f7
/* global process */
import * as redux from 'redux'; import * as redux from 'redux';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import immutable from '../util/immutable';
import { createReducer, bindSelectors } from './util'; import { createReducer, bindSelectors } from './util';
/** /**
...@@ -56,14 +60,21 @@ export default function createStore(modules, initArgs = [], middleware = []) { ...@@ -56,14 +60,21 @@ export default function createStore(modules, initArgs = [], middleware = []) {
// asynchronous (see https://github.com/gaearon/redux-thunk#motivation) // asynchronous (see https://github.com/gaearon/redux-thunk#motivation)
thunk, thunk,
]; ];
const enhancer = redux.applyMiddleware(...defaultMiddleware, ...middleware); const enhancer = redux.applyMiddleware(...defaultMiddleware, ...middleware);
// Create the combined reducer from the reducers for each module.
let reducer = redux.combineReducers(allReducers);
// In debug builds, freeze the new state after each action to catch any attempts
// to mutate it, which indicates a bug since it is supposed to be immutable.
if (process.env.NODE_ENV !== 'production') {
const originalReducer = reducer;
reducer = (state, action) => immutable(originalReducer(state, action));
}
// Create the store. // Create the store.
const store = redux.createStore( const store = redux.createStore(reducer, initialState, enhancer);
redux.combineReducers(allReducers),
initialState,
enhancer
);
// Add actions and selectors as methods to the store. // Add actions and selectors as methods to the store.
const actions = Object.assign({}, ...modules.map(m => m.actions)); const actions = Object.assign({}, ...modules.map(m => m.actions));
......
/* global process */
import createStore from '../create-store'; import createStore from '../create-store';
const A = 0; const A = 0;
...@@ -159,4 +161,13 @@ describe('sidebar/store/create-store', () => { ...@@ -159,4 +161,13 @@ describe('sidebar/store/create-store', () => {
assert.equal(store.getState().a.count, 0); assert.equal(store.getState().a.count, 0);
assert.equal(store.getState().b.count, 0); assert.equal(store.getState().b.count, 0);
}); });
if (process.env.NODE_ENV !== 'production') {
it('freezes store state in development builds', () => {
const store = counterStore();
assert.throws(() => {
store.getState().a.count = 1;
}, /Cannot assign to read only property/);
});
}
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment