Redux: Add Redux Toolkit

This commit adds Redux Toolkit as a dependency. Adding Redux Toolkit is
beneficial because it will allow us to use the current best-practice Redux
development patterns and tools as well as reduce the number of
dependencies. Redux Toolkit is backwards compatible with existing Redux code,
and therefore works with all of the current actions and reducers.

The store is now created using Redux Toolkit's configureStore(). Previously, a
custom store creator developed by Red Hat Insights was used. However,
this is actually not required for Insights apps, and creating the store using
configureStore() is necessary to take advantage of Redux Toolkit. For
instance, the store can now be inspected using the Redux devtools in the
browser.

This commit removes the redux-logger middleware. It is no longer
necessary, as the Redux development tools can now be used to easily inspect and
reason about the redux store and its state.

The Thunk middleware dependency has also been removed, as Thunks are already
included in Redux Toolkit.

The redux-promise-middleware dependency has been left in place for now,
but its functionality is also available in Redux Toolkit and it may be
considered for removal in the future.

Using Redux Toolkit will also allow us to move to the `Slice` pattern when
defining actions/reducers in the future if we wish. This will make
writing, reasoning about, and debugging the code related to the Redux
store much easier.
This commit is contained in:
lucasgarfield 2022-08-30 17:42:50 +02:00 committed by Sanne Raymaekers
parent 1d6a92a7f9
commit a977f4b72b
6 changed files with 85 additions and 81 deletions

89
package-lock.json generated
View file

@ -16,15 +16,14 @@
"@redhat-cloud-services/frontend-components": "3.9.2",
"@redhat-cloud-services/frontend-components-notifications": "3.2.7",
"@redhat-cloud-services/frontend-components-utilities": "3.2.16",
"@reduxjs/toolkit": "^1.8.5",
"classnames": "2.3.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-redux": "8.0.2",
"react-router-dom": "6.3.0",
"redux": "4.1.2",
"redux-logger": "3.0.6",
"redux-promise-middleware": "6.1.2",
"redux-thunk": "2.4.1"
"redux-promise-middleware": "6.1.2"
},
"devDependencies": {
"@babel/core": "7.18.6",
@ -3229,6 +3228,29 @@
"resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-0.0.1.tgz",
"integrity": "sha512-UgHgLf8LkqaD9PXjiVyYycMRlbvmVdJyoJfersCUwnzDMgp+DEoYlLQAa9kn/s4c1JvSt5MM+hEN+DRvwtCQGA=="
},
"node_modules/@reduxjs/toolkit": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.5.tgz",
"integrity": "sha512-f4D5EXO7A7Xq35T0zRbWq5kJQyXzzscnHKmjnu2+37B3rwHU6mX9PYlbfXdnxcY6P/7zfmjhgan0Z+yuOfeBmA==",
"dependencies": {
"immer": "^9.0.7",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"reselect": "^4.1.5"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
"react-redux": "^7.2.1 || ^8.0.2"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@scalprum/core": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@scalprum/core/-/core-0.1.2.tgz",
@ -6422,11 +6444,6 @@
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
"dev": true
},
"node_modules/deep-diff": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz",
"integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ="
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -8761,6 +8778,15 @@
"node": ">= 4"
}
},
"node_modules/immer": {
"version": "9.0.15",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz",
"integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/immutable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
@ -13822,14 +13848,6 @@
"@babel/runtime": "^7.9.2"
}
},
"node_modules/redux-logger": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz",
"integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=",
"dependencies": {
"deep-diff": "^0.3.5"
}
},
"node_modules/redux-mock-store": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz",
@ -14014,6 +14032,11 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"node_modules/reselect": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
"integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
},
"node_modules/resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@ -19140,6 +19163,17 @@
"resolved": "https://registry.npmjs.org/@redhat-cloud-services/types/-/types-0.0.1.tgz",
"integrity": "sha512-UgHgLf8LkqaD9PXjiVyYycMRlbvmVdJyoJfersCUwnzDMgp+DEoYlLQAa9kn/s4c1JvSt5MM+hEN+DRvwtCQGA=="
},
"@reduxjs/toolkit": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.5.tgz",
"integrity": "sha512-f4D5EXO7A7Xq35T0zRbWq5kJQyXzzscnHKmjnu2+37B3rwHU6mX9PYlbfXdnxcY6P/7zfmjhgan0Z+yuOfeBmA==",
"requires": {
"immer": "^9.0.7",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"reselect": "^4.1.5"
}
},
"@scalprum/core": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@scalprum/core/-/core-0.1.2.tgz",
@ -21704,11 +21738,6 @@
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
"dev": true
},
"deep-diff": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz",
"integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ="
},
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -23453,6 +23482,11 @@
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"dev": true
},
"immer": {
"version": "9.0.15",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.15.tgz",
"integrity": "sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ=="
},
"immutable": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
@ -27180,14 +27214,6 @@
"@babel/runtime": "^7.9.2"
}
},
"redux-logger": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz",
"integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=",
"requires": {
"deep-diff": "^0.3.5"
}
},
"redux-mock-store": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz",
@ -27334,6 +27360,11 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"reselect": {
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
"integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
},
"resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",

View file

@ -15,15 +15,14 @@
"@redhat-cloud-services/frontend-components": "3.9.2",
"@redhat-cloud-services/frontend-components-notifications": "3.2.7",
"@redhat-cloud-services/frontend-components-utilities": "3.2.16",
"@reduxjs/toolkit": "^1.8.5",
"classnames": "2.3.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-redux": "8.0.2",
"react-router-dom": "6.3.0",
"redux": "4.1.2",
"redux-logger": "3.0.6",
"redux-promise-middleware": "6.1.2",
"redux-thunk": "2.4.1"
"redux-promise-middleware": "6.1.2"
},
"jest": {
"coverageDirectory": "./coverage/",

View file

@ -1,18 +1,12 @@
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import { init } from './store';
import App from './App';
import { getBaseName } from '@redhat-cloud-services/frontend-components-utilities/helpers';
import logger from 'redux-logger';
import { store } from './store';
const ImageBuilder = () => (
<Provider
store={init(
{},
...[process.env.NODE_ENV !== 'production' ? logger : undefined]
).getStore()}
>
<Provider store={store}>
<Router basename={getBaseName(window.location.pathname)}>
<App />
</Router>

View file

@ -1,38 +1,14 @@
import { ReducerRegistry } from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry';
import { configureStore } from '@reduxjs/toolkit';
import composes from './reducers/composes';
import promiseMiddleware from 'redux-promise-middleware';
import thunk from 'redux-thunk';
import { notificationsReducer } from '@redhat-cloud-services/frontend-components-notifications/redux';
import composes from './reducers/composes';
export const reducer = {
composes: composes,
notifications: notificationsReducer,
};
let registry;
export const middleware = (getDefaultMiddleware) =>
getDefaultMiddleware().concat(promiseMiddleware);
export function init(store = {}, ...middleware) {
if (!registry) {
registry = new ReducerRegistry(store, [
promiseMiddleware,
thunk,
...middleware.filter((item) => typeof item !== 'undefined'),
]);
registry.register({
composes,
notifications: notificationsReducer,
});
}
return registry;
}
export function getStore() {
return registry.getStore();
}
export function register(...args) {
return registry.register(...args);
}
/* added for testing purposes only */
export function clearStore() {
registry = undefined;
}
export const store = configureStore({ reducer, middleware });

View file

@ -1343,7 +1343,7 @@ describe('Click through all steps', () => {
const setUp = async () => {
const view = renderWithReduxRouter(<CreateImageWizard />);
history = view.history;
store = view.reduxStore;
store = view.store;
};
test('with valid values', async () => {
@ -1767,7 +1767,7 @@ describe('Click through all steps', () => {
// returns back to the landing page
await waitFor(() => expect(history.location.pathname).toBe('/'));
expect(store.getStore().getState().composes.allIds).toEqual(ids);
expect(store.getState().composes.allIds).toEqual(ids);
// set test timeout of 10 seconds
}, 10000);
});

View file

@ -1,23 +1,27 @@
import React from 'react';
import { Router } from 'react-router-dom';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { init, clearStore } from '../store';
import { reducer, middleware } from '../store';
export const renderWithReduxRouter = (component, store = {}, route = '/') => {
export const renderWithReduxRouter = (
component,
preloadedState = {},
route = '/'
) => {
const history = createMemoryHistory({ initialEntries: [route] });
clearStore();
let reduxStore = init(store);
const store = configureStore({ reducer, middleware, preloadedState });
return {
...render(
<Provider store={reduxStore.getStore()}>
<Provider store={store}>
<Router location={history.location} navigator={history}>
{component}
</Router>
</Provider>
),
history,
reduxStore,
store,
};
};