Reselect is very useful if you use Redux in your app, but debugging it can sometimes be difficult (at least as of this blog post). This difficulty is often overcome by unit testing your selectors. That being said, sometimes you want to make a quick change to try something out without having to go back and fix your tests.
To prevent excessive console.log()
-ing, I thought that it would be useful to be able to see how the
selectors looked using the Redux DevTools. So, I
essentially decided to create a new reducer that only loads during development mode that will show
us how the selectors look after each state update.
To do this, we load our reducer asynchronously if we are in development mode. Each of its members will be each one of the selectors that we want to check on. Using webpack, we can inject an environment variable into our bundle that lets us determine what mode we are in.
webpack.config.babel.js
// imports section
import webpack from 'webpack';
// inside your plugins section
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
We want to update the reducer every time the state changes. This will be different for you depending on how your project is set up. For my project, I am using redux-saga to handle side effects and it happens to suit this purpose well.
So to update the reducer, we simply create a new saga and import all of my selectors that we want to add to the dev tools, and then export a generator function that listens for every single action that fires. It will then pass the updated state into each of our selectors which will then update our asynchronous reducer.
DevTools/reducer.js
import { UPDATE_SELECTORS_SUCCESS } from './constants';
const initialState = {
selectors: {},
};
export default function (state = initialState, action) {
switch (action.type) {
case UPDATE_SELECTORS_SUCCESS:
return { ...state, selectors: action.payload };
default:
return state;
}
}
DevTools/actions.js
import { UPDATE_SELECTORS_SUCCESS } from './constants';
export const updateSelectors = (payload) => ({ type: UPDATE_SELECTORS_SUCCESS, payload });
DevTools/saga.js
import { put, take, select } from 'redux-saga/effects';
import { createSelector } from 'reselect';
import { updateSelectors } from './actions';
import chartDataSelector from 'chartDataSelector';
import dashboardSelector from 'dashboardSelector';
import metricNameSelector from 'metricNameSelector';
import userSelector from 'userSelector';
import predictionSelector from 'predictionSelector';
const selectors = [
chartDataSelector,
dashboardSelector,
metricNameSelector,
userSelector,
predictionSelector,
].map(selector => ({ name: selector.name, selector: selector() }));
export default function* updateSelectorSaga() {
while (true) {
try {
yield take('*');
const selectorsState = yield select(createSelector(
selectors.map(s => s.selector),
(...results) => results.reduce((o, result, idx) => ({
...o, [selectors[idx].name]: result,
}), {})
));
yield put(updateSelectors(selectorsState));
} catch (err) {
console.log(err);
// here we simply log any errors, although you could also consider putting the error into
// the state.
}
}
}
We can use import()
(or System.import()
if you're on an older version of webpack) to load our
reducer and saga. This not only loads them asynchronously, but will cause webpack to split the
code into a separate bundle so it doesn't
increase the size of your production build.
index.js
// with async/await and import()
const loadDevHelpers = async () => {
const [devSagas, devReducer] = await Promise.all([
import('DevTools/saga'),
import('DevTools/reducer'),
]);
injectAsyncReducer(store, 'debug', devReducer.default);
sagaMiddleware.run(devSagas.default);
};
// or if you want to use Promises and System.import()
const loadDevHelpers = () => {
Promise.all([
System.import('DevTools/saga'),
System.import('DevTools/reducer'),
]).then(([devSagas, devReducer]) => {
injectAsyncReducer(store, 'debug', devReducer.default);
sagaMiddleware.run(devSagas.default);
});
};
// using Promises and System.import() (for older versions of webpack)
if (process.env.NODE_ENV !== 'production') {
loadDevHelpers();
}
And that’s all we have to do to check on our selectors inside the redux dev tools. You’ll notice all of the selectors that you have imported will now show up.