Skip to content

Commit 61f375d

Browse files
Immer (#2380)
Migrating react-boilerplate to immer! Immediate benefits: - Immutable data without the need to use Immutable's custom data types - Essentially no learning curve - Removes over 50k from our builds - Trivial to remove for anyone who doesn't want it. We could even add it as an option in the generators. Co-authored-by: null <[email protected]>
1 parent 323ab6d commit 61f375d

36 files changed

+466
-650
lines changed

app/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import '@babel/polyfill';
1212
import React from 'react';
1313
import ReactDOM from 'react-dom';
1414
import { Provider } from 'react-redux';
15-
import { ConnectedRouter } from 'connected-react-router/immutable';
15+
import { ConnectedRouter } from 'connected-react-router';
1616
import FontFaceObserver from 'fontfaceobserver';
1717
import history from 'utils/history';
1818
import 'sanitize.css/sanitize.css';

app/configureStore.js

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,31 @@
33
*/
44

55
import { createStore, applyMiddleware, compose } from 'redux';
6-
import { fromJS } from 'immutable';
7-
import { routerMiddleware } from 'connected-react-router/immutable';
6+
import { routerMiddleware } from 'connected-react-router';
87
import createSagaMiddleware from 'redux-saga';
98
import createReducer from './reducers';
109

1110
export default function configureStore(initialState = {}, history) {
12-
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
13-
/* eslint-disable no-underscore-dangle, indent */
14-
const composeEnhancers =
15-
process.env.NODE_ENV !== 'production' &&
16-
typeof window === 'object' &&
17-
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
18-
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
19-
: compose;
20-
21-
// NOTE: Uncomment the code below to restore support for Redux Saga
22-
// Dev Tools once it supports redux-saga version 1.x.x
23-
24-
// const reduxSagaMonitorOptions =
25-
// process.env.NODE_ENV !== 'production' &&
26-
// typeof window === 'object' &&
27-
// window.__SAGA_MONITOR_EXTENSION__
28-
// ? { sagaMonitor: window.__SAGA_MONITOR_EXTENSION__ }
29-
// : {};
30-
/* eslint-enable */
31-
32-
// const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
33-
const sagaMiddleware = createSagaMiddleware({});
11+
let composeEnhancers = compose;
12+
const reduxSagaMonitorOptions = {};
13+
14+
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
15+
/* istanbul ignore next */
16+
if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
17+
/* eslint-disable no-underscore-dangle */
18+
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__)
19+
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
20+
21+
// NOTE: Uncomment the code below to restore support for Redux Saga
22+
// Dev Tools once it supports redux-saga version 1.x.x
23+
// if (window.__SAGA_MONITOR_EXTENSION__)
24+
// reduxSagaMonitorOptions = {
25+
// sagaMonitor: window.__SAGA_MONITOR_EXTENSION__,
26+
// };
27+
/* eslint-enable */
28+
}
29+
30+
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
3431

3532
// Create the store with two middlewares
3633
// 1. sagaMiddleware: Makes redux-sagas work
@@ -41,7 +38,7 @@ export default function configureStore(initialState = {}, history) {
4138

4239
const store = createStore(
4340
createReducer(),
44-
fromJS(initialState),
41+
initialState,
4542
composeEnhancers(...enhancers),
4643
);
4744

app/containers/App/reducer.js

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,46 @@
11
/*
22
* AppReducer
33
*
4-
* The reducer takes care of our data. Using actions, we can change our
5-
* application state.
6-
* To add a new action, add it to the switch statement in the reducer function
4+
* The reducer takes care of our data. Using actions, we can
5+
* update our application state. To add a new action,
6+
* add it to the switch statement in the reducer function
77
*
8-
* Example:
9-
* case YOUR_ACTION_CONSTANT:
10-
* return state.set('yourStateVariable', true);
118
*/
129

13-
import { fromJS } from 'immutable';
14-
10+
import produce from 'immer';
1511
import { LOAD_REPOS_SUCCESS, LOAD_REPOS, LOAD_REPOS_ERROR } from './constants';
1612

1713
// The initial state of the App
18-
const initialState = fromJS({
14+
export const initialState = {
1915
loading: false,
2016
error: false,
2117
currentUser: false,
2218
userData: {
2319
repositories: false,
2420
},
25-
});
21+
};
22+
23+
/* eslint-disable default-case, no-param-reassign */
24+
const appReducer = (state = initialState, action) =>
25+
produce(state, draft => {
26+
switch (action.type) {
27+
case LOAD_REPOS:
28+
draft.loading = true;
29+
draft.error = false;
30+
draft.userData.repositories = false;
31+
break;
32+
33+
case LOAD_REPOS_SUCCESS:
34+
draft.userData.repositories = action.repos;
35+
draft.loading = false;
36+
draft.currentUser = action.username;
37+
break;
2638

27-
function appReducer(state = initialState, action) {
28-
switch (action.type) {
29-
case LOAD_REPOS:
30-
return state
31-
.set('loading', true)
32-
.set('error', false)
33-
.setIn(['userData', 'repositories'], false);
34-
case LOAD_REPOS_SUCCESS:
35-
return state
36-
.setIn(['userData', 'repositories'], action.repos)
37-
.set('loading', false)
38-
.set('currentUser', action.username);
39-
case LOAD_REPOS_ERROR:
40-
return state.set('error', action.error).set('loading', false);
41-
default:
42-
return state;
43-
}
44-
}
39+
case LOAD_REPOS_ERROR:
40+
draft.error = action.error;
41+
draft.loading = false;
42+
break;
43+
}
44+
});
4545

4646
export default appReducer;

app/containers/App/selectors.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,40 @@
33
*/
44

55
import { createSelector } from 'reselect';
6+
import { initialState } from './reducer';
67

7-
const selectGlobal = state => state.get('global');
8+
const selectGlobal = state => state.global || initialState;
89

9-
const selectRouter = state => state.get('router');
10+
const selectRouter = state => state.router;
1011

1112
const makeSelectCurrentUser = () =>
1213
createSelector(
1314
selectGlobal,
14-
globalState => globalState.get('currentUser'),
15+
globalState => globalState.currentUser,
1516
);
1617

1718
const makeSelectLoading = () =>
1819
createSelector(
1920
selectGlobal,
20-
globalState => globalState.get('loading'),
21+
globalState => globalState.loading,
2122
);
2223

2324
const makeSelectError = () =>
2425
createSelector(
2526
selectGlobal,
26-
globalState => globalState.get('error'),
27+
globalState => globalState.error,
2728
);
2829

2930
const makeSelectRepos = () =>
3031
createSelector(
3132
selectGlobal,
32-
globalState => globalState.getIn(['userData', 'repositories']),
33+
globalState => globalState.userData.repositories,
3334
);
3435

3536
const makeSelectLocation = () =>
3637
createSelector(
3738
selectRouter,
38-
routerState => routerState.get('location').toJS(),
39+
routerState => routerState.location,
3940
);
4041

4142
export {

app/containers/App/tests/reducer.test.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
import { fromJS } from 'immutable';
1+
import produce from 'immer';
22

33
import appReducer from '../reducer';
44
import { loadRepos, reposLoaded, repoLoadingError } from '../actions';
55

6+
/* eslint-disable default-case, no-param-reassign */
67
describe('appReducer', () => {
78
let state;
89
beforeEach(() => {
9-
state = fromJS({
10+
state = {
1011
loading: false,
1112
error: false,
1213
currentUser: false,
13-
userData: fromJS({
14+
userData: {
1415
repositories: false,
15-
}),
16-
});
16+
},
17+
};
1718
});
1819

1920
it('should return the initial state', () => {
@@ -22,10 +23,11 @@ describe('appReducer', () => {
2223
});
2324

2425
it('should handle the loadRepos action correctly', () => {
25-
const expectedResult = state
26-
.set('loading', true)
27-
.set('error', false)
28-
.setIn(['userData', 'repositories'], false);
26+
const expectedResult = produce(state, draft => {
27+
draft.loading = true;
28+
draft.error = false;
29+
draft.userData.repositories = false;
30+
});
2931

3032
expect(appReducer(state, loadRepos())).toEqual(expectedResult);
3133
});
@@ -37,10 +39,11 @@ describe('appReducer', () => {
3739
},
3840
];
3941
const username = 'test';
40-
const expectedResult = state
41-
.setIn(['userData', 'repositories'], fixture)
42-
.set('loading', false)
43-
.set('currentUser', username);
42+
const expectedResult = produce(state, draft => {
43+
draft.userData.repositories = fixture;
44+
draft.loading = false;
45+
draft.currentUser = username;
46+
});
4447

4548
expect(appReducer(state, reposLoaded(fixture, username))).toEqual(
4649
expectedResult,
@@ -51,7 +54,10 @@ describe('appReducer', () => {
5154
const fixture = {
5255
msg: 'Not found',
5356
};
54-
const expectedResult = state.set('error', fixture).set('loading', false);
57+
const expectedResult = produce(state, draft => {
58+
draft.error = fixture;
59+
draft.loading = false;
60+
});
5561

5662
expect(appReducer(state, repoLoadingError(fixture))).toEqual(
5763
expectedResult,
Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { fromJS } from 'immutable';
2-
31
import {
42
selectGlobal,
53
makeSelectCurrentUser,
@@ -11,10 +9,10 @@ import {
119

1210
describe('selectGlobal', () => {
1311
it('should select the global state', () => {
14-
const globalState = fromJS({});
15-
const mockedState = fromJS({
12+
const globalState = {};
13+
const mockedState = {
1614
global: globalState,
17-
});
15+
};
1816
expect(selectGlobal(mockedState)).toEqual(globalState);
1917
});
2018
});
@@ -23,11 +21,11 @@ describe('makeSelectCurrentUser', () => {
2321
const currentUserSelector = makeSelectCurrentUser();
2422
it('should select the current user', () => {
2523
const username = 'mxstbr';
26-
const mockedState = fromJS({
24+
const mockedState = {
2725
global: {
2826
currentUser: username,
2927
},
30-
});
28+
};
3129
expect(currentUserSelector(mockedState)).toEqual(username);
3230
});
3331
});
@@ -36,11 +34,11 @@ describe('makeSelectLoading', () => {
3634
const loadingSelector = makeSelectLoading();
3735
it('should select the loading', () => {
3836
const loading = false;
39-
const mockedState = fromJS({
37+
const mockedState = {
4038
global: {
4139
loading,
4240
},
43-
});
41+
};
4442
expect(loadingSelector(mockedState)).toEqual(loading);
4543
});
4644
});
@@ -49,38 +47,39 @@ describe('makeSelectError', () => {
4947
const errorSelector = makeSelectError();
5048
it('should select the error', () => {
5149
const error = 404;
52-
const mockedState = fromJS({
50+
const mockedState = {
5351
global: {
5452
error,
5553
},
56-
});
54+
};
5755
expect(errorSelector(mockedState)).toEqual(error);
5856
});
5957
});
6058

6159
describe('makeSelectRepos', () => {
6260
const reposSelector = makeSelectRepos();
6361
it('should select the repos', () => {
64-
const repositories = fromJS([]);
65-
const mockedState = fromJS({
62+
const repositories = [];
63+
const mockedState = {
6664
global: {
6765
userData: {
6866
repositories,
6967
},
7068
},
71-
});
69+
};
7270
expect(reposSelector(mockedState)).toEqual(repositories);
7371
});
7472
});
7573

7674
describe('makeSelectLocation', () => {
7775
const locationStateSelector = makeSelectLocation();
7876
it('should select the location', () => {
79-
const mockedState = fromJS({
80-
router: { location: { pathname: '/foo' } },
81-
});
82-
expect(locationStateSelector(mockedState)).toEqual(
83-
mockedState.getIn(['router', 'location']).toJS(),
84-
);
77+
const router = {
78+
location: { pathname: '/foo' },
79+
};
80+
const mockedState = {
81+
router,
82+
};
83+
expect(locationStateSelector(mockedState)).toEqual(router.location);
8584
});
8685
});

0 commit comments

Comments
 (0)