Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Redux Toolkit #638

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
"license": "MIT",
"dependencies": {
"@hot-loader/react-dom": "16.10.2",
"@reduxjs/toolkit": "1.0.4",
"connected-react-router": "6.5.2",
"object-assign": "4.1.1",
"react": "16.11.0",
"react-dom": "16.11.0",
"react-redux": "7.1.1",
"react-router-dom": "5.1.2",
"redux": "4.0.4",
"redux-thunk": "2.3.0"
"redux": "4.0.4"
},
"devDependencies": {
"@babel/cli": "7.6.4",
Expand Down Expand Up @@ -91,7 +91,6 @@
"raf": "3.4.1",
"react-hot-loader": "4.12.15",
"react-test-renderer": "16.11.0",
"redux-immutable-state-invariant": "2.1.0",
"redux-mock-store": "1.5.3",
"replace": "1.1.1",
"rimraf": "3.0.0",
Expand Down
19 changes: 9 additions & 10 deletions src/components/containers/FuelSavingsPage.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as actions from '../../actions/fuelSavingsActions';
import {calculateFuelSavings, saveFuelSavings} from '../../actions/fuelSavingsActions';
import FuelSavingsForm from '../FuelSavingsForm';

export class FuelSavingsPage extends React.Component {
saveFuelSavings = () => {
this.props.actions.saveFuelSavings(this.props.fuelSavings);
this.props.saveFuelSavings(this.props.fuelSavings);
}

calculateFuelSavings = e => {
this.props.actions.calculateFuelSavings(this.props.fuelSavings, e.target.name, e.target.value);
this.props.calculateFuelSavings(this.props.fuelSavings, e.target.name, e.target.value);
}

render() {
Expand All @@ -26,7 +25,8 @@ export class FuelSavingsPage extends React.Component {
}

FuelSavingsPage.propTypes = {
actions: PropTypes.object.isRequired,
saveFuelSavings: PropTypes.func.isRequired,
calculateFuelSavings: PropTypes.func.isRequired,
fuelSavings: PropTypes.object.isRequired
};

Expand All @@ -36,13 +36,12 @@ function mapStateToProps(state) {
};
}

function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
const mapDispatchToProps = {
calculateFuelSavings,
saveFuelSavings,
}

export default connect(
mapStateToProps,
mapDispatchToProps
mapDispatchToProps,
)(FuelSavingsPage);
6 changes: 3 additions & 3 deletions src/components/containers/FuelSavingsPage.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("<FuelSavingsPage />", () => {
it("should contain <FuelSavingsForm />", () => {
const wrapper = shallow(
<FuelSavingsPage
actions={actions}
{...actions}
fuelSavings={initialState.fuelSavings}
/>
);
Expand All @@ -27,7 +27,7 @@ describe("<FuelSavingsPage />", () => {
it("calls saveFuelSavings upon clicking save", () => {
const wrapper = mount(
<FuelSavingsPage
actions={actions}
{...actions}
fuelSavings={initialState.fuelSavings}
/>
);
Expand All @@ -43,7 +43,7 @@ describe("<FuelSavingsPage />", () => {
it("calls calculateFuelSavings upon changing a field", () => {
const wrapper = mount(
<FuelSavingsPage
actions={actions}
{...actions}
fuelSavings={initialState.fuelSavings}
/>
);
Expand Down
46 changes: 46 additions & 0 deletions src/features/fuelSavings/fuelSavingsSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {createSlice} from "@reduxjs/toolkit";
import {necessaryDataIsProvidedToCalculateSavings, calculateSavings} from '../../utils/fuelSavings';

const initialState = {
newMpg: '',
tradeMpg: '',
newPpg: '',
tradePpg: '',
milesDriven: '',
milesDrivenTimeframe: 'week',
displayResults: false,
dateModified: null,
necessaryDataIsProvidedToCalculateSavings: false,
savings: {
monthly: 0,
annual: 0,
threeYear: 0
}
}

const fuelSavingsSlice = createSlice({
name: "fuelSavings",
initialState,
reducers: {
saveFuelSavings(state, action) {
// For this example, just simulating a save by changing date modified.
// In a real app using Redux, you might use redux-thunk and handle the async call in fuelSavingsActions.js
state.dateModified = action.payload.dateModified;
},
calculateFuelSavings(state, action) {
const {fieldName, value, dateModified} = action.payload;

state[fieldName] = value;
state.necessaryDataIsProvidedToCalculateSavings = necessaryDataIsProvidedToCalculateSavings(state);
state.dateModified = dateModified;

if(state.necessaryDataIsProvidedToCalculateSavings) {
state.savings = calculateSavings(state);
}
}
}
})

export const {saveFuelSavings, calculateFuelSavings} = fuelSavingsSlice.actions;

export default fuelSavingsSlice.reducer;
67 changes: 67 additions & 0 deletions src/features/fuelSavings/fuelSavingsSlice.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import reducer, {saveFuelSavings, calculateFuelSavings} from './fuelSavingsSlice';
import {getFormattedDateTime} from '../../utils/dates';

describe('Reducers::FuelSavings', () => {
const getInitialState = () => {
return {
newMpg: '',
tradeMpg: '',
newPpg: '',
tradePpg: '',
milesDriven: '',
milesDrivenTimeframe: 'week',
displayResults: false,
dateModified: null,
necessaryDataIsProvidedToCalculateSavings: false,
savings: {
monthly: 0,
annual: 0,
threeYear: 0
}
};
};

const getAppState = () => {
return {
newMpg: 20,
tradeMpg: 10,
newPpg: 1.50,
tradePpg: 1.50,
milesDriven: 100,
milesDrivenTimeframe: 'week',
displayResults: false,
dateModified: null,
necessaryDataIsProvidedToCalculateSavings: false,
savings: {
monthly: 0,
annual: 0,
threeYear: 0
}
};
};
const dateModified = getFormattedDateTime();

it('should set initial state by default', () => {
const action = { type: 'unknown' };
const expected = getInitialState();

expect(reducer(undefined, action)).toEqual(expected);
});

it('should handle saveFuelSavings', () => {
const action = saveFuelSavings({dateModified, settings: getAppState() })
const expected = Object.assign(getAppState(), { dateModified });

expect(reducer(getAppState(), action)).toEqual(expected);
});

it('should handle calculateFuelSavings', () => {
const action = calculateFuelSavings({dateModified, settings: getAppState(), fieldName: 'newMpg', value: 30 });

const expectedMpg = 30;
const expectedSavings = { monthly: '$43.33', annual: '$519.96', threeYear: '$1,559.88' };

expect(reducer(getAppState(), action).newMpg).toEqual(expectedMpg);
expect(reducer(getAppState(), action).savings).toEqual(expectedSavings);
});
})
4 changes: 2 additions & 2 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { combineReducers } from 'redux';
import fuelSavings from './fuelSavingsReducer';
import { connectRouter } from 'connected-react-router'

const rootReducer = history => combineReducers({
const createRootReducer = history => combineReducers({
router: connectRouter(history),
fuelSavings,
});

export default rootReducer;
export default createRootReducer;
54 changes: 12 additions & 42 deletions src/store/configureStore.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {createStore, compose, applyMiddleware} from 'redux';
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
import thunk from 'redux-thunk';
import {configureStore, getDefaultMiddleware} from "@reduxjs/toolkit"
import { createBrowserHistory } from "history";
// 'routerMiddleware': the new way of storing route changes with redux middleware since rrV4.
import { connectRouter, routerMiddleware } from 'connected-react-router';
Expand All @@ -9,46 +7,20 @@ import createRootReducer from '../reducers';
export const history = createBrowserHistory();
const connectRouterHistory = connectRouter(history);

function configureStoreProd(initialState) {
function configureAppStore(initialState) {
const reactRouterMiddleware = routerMiddleware(history);
const middlewares = [
// Add other middleware on this line...

// thunk middleware can also accept an extra argument to be passed to each thunk action
// https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument
thunk,
reactRouterMiddleware,
];
const isProduction = process.env.NODE_ENV === 'production';

return createStore(
createRootReducer(history), // root reducer with router state
initialState,
compose(applyMiddleware(...middlewares))
);
}

function configureStoreDev(initialState) {
const reactRouterMiddleware = routerMiddleware(history);
const middlewares = [
// Add other middleware on this line...

// Redux middleware that spits an error on you when you try to mutate your state either inside a dispatch or between dispatches.
reduxImmutableStateInvariant(),
const store = configureStore({
reducer: createRootReducer(history), // root reducer with router state,
preloadedState: initialState,
// customize the default middleware via options if desired
middleware: [...getDefaultMiddleware(), reactRouterMiddleware],
devTools: !isProduction,
})

// thunk middleware can also accept an extra argument to be passed to each thunk action
// https://github.com/reduxjs/redux-thunk#injecting-a-custom-argument
thunk,
reactRouterMiddleware,
];

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // add support for Redux dev tools
const store = createStore(
createRootReducer(history), // root reducer with router state
initialState,
composeEnhancers(applyMiddleware(...middlewares))
);

if (module.hot) {
if (!isProduction && module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default; // eslint-disable-line global-require
Expand All @@ -59,6 +31,4 @@ function configureStoreDev(initialState) {
return store;
}

const configureStore = process.env.NODE_ENV === 'production' ? configureStoreProd : configureStoreDev;

export default configureStore;
export default configureAppStore;
33 changes: 30 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,18 @@
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^13.0.0"

"@reduxjs/[email protected]":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.0.4.tgz#cec88446a22a98b48808af7ab19a58aa93e6f5f9"
integrity sha512-nyCZ9/CpnMXFZ//0wm1mNPSEl0J0bCghY2qeHM8zuubaBBMBr6KsIaLLms1jThbOJ1O+Ej0Tl11z5naE9czfzA==
dependencies:
immer "^4.0.1"
redux "^4.0.0"
redux-devtools-extension "^2.13.8"
redux-immutable-state-invariant "^2.1.0"
redux-thunk "^2.3.0"
reselect "^4.0.0"

"@types/babel__core@^7.1.0":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30"
Expand Down Expand Up @@ -4639,6 +4651,11 @@ ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==

immer@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/immer/-/immer-4.0.2.tgz#9ff0fcdf88e06f92618a5978ceecb5884e633559"
integrity sha512-Q/tm+yKqnKy4RIBmmtISBlhXuSDrB69e9EKTYiIenIKQkXBQir43w+kN/eGiax3wt1J0O1b2fYcNqLSbEcXA7w==

immutable@^3, immutable@^3.8.1:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
Expand Down Expand Up @@ -8069,7 +8086,12 @@ redent@^2.0.0:
indent-string "^3.0.0"
strip-indent "^2.0.0"

[email protected]:
redux-devtools-extension@^2.13.8:
version "2.13.8"
resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1"
integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg==

redux-immutable-state-invariant@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1"
integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg==
Expand All @@ -8084,12 +8106,12 @@ [email protected]:
dependencies:
lodash.isplainobject "^4.0.6"

[email protected]:
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==

[email protected]:
[email protected], redux@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796"
integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==
Expand Down Expand Up @@ -8272,6 +8294,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=

reselect@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==

resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
Expand Down