Skip to content

Instantly share code, notes, and snippets.

@bstro
Last active May 21, 2018 12:45
Show Gist options
  • Save bstro/2366fc8fae5c99803f942baaef161aac to your computer and use it in GitHub Desktop.
Save bstro/2366fc8fae5c99803f942baaef161aac to your computer and use it in GitHub Desktop.
simple selector middleware devtool

This is a simple pattern I follow that helps me maintain an easily accessible graph of all derived data flowing through a Redux application, and expose that derived data (as well as the Redux state itself) to the global window object in my browser's devtools.

selectorMiddleware.js

computes selectors and sets the derived data on the window object, visible at window.selectors

stateMiddleware.js

simply exposes the redux state to the window object at window.state. nothing crazy here

configureStore.js

apply the two middlewares

selectors.js

an example of how I write selectors.

import selectorMiddleware from './selectorMiddleware.js';
import stateMiddleware from './stateMiddleware.js';
export default createStore(
combineReducers({ … }),
applyMiddleware(selectorMiddleware, stateMiddleware, …)
);
import selectors as * from '/selectors.js';
const selectorMiddleware = ({ getState }) => next => action => {
const ret = next(action);
// Don't run this code in prod.
if (process.env.NODE_ENV !== 'production') {
const selectorKeys = Object.keys(selectors);
window.selectors = {};
// Iterate through all the selectors and compute their state.
for (let key of selectorKeys) {
const selector = selectors[key];
if (!fp.isFunction(selector)) continue;
const value = selector(getState());
// This is a dumb way of filtering out prop-based selectors
// (we can't compute them as they are coupled to the lifecycle of a component instance).
if (fp.isNil(value)) continue;
window.selectors[key] = value;
}
}
};
import createSelector from 'reselect';
import fp from 'lodash/fp';
export getState = state => state;
export getProps = (_, props) => props;
// ^ just including this to mention I like to make prop access explicit, as it reminds
// me there's an explicit coupling introduced by using component props in a selector.
export const getEntities => createSelector(getState, fp.get('entities'));
export const getArticles => createSelector(getEntities, fp.get('articles'));
export const getArticleIdFromProps => createSelector(getProps, fp.get('activeArticleId'));
// ^ I also like to explicity indicate (in the selector name) when
// a select is reliant on a prop in order to expose that coupling.
export const getArticle => createSelector(
getArticleIdFromProps,
getArticles,
fp.get // lodash/fp makes point-free function application particularly easy here.
);
/*
These selectors then get exposed on window.selectors in debug mode.
Prop-based selectors are ignored, thus window.selectors.getEntities &
window.selectors.getArticles will be computed, but getArticle and
getArticleIdFromProps will not.
A potential solution that I often use is to store route params in
Redux, so we could compute `activeArticle` using a selector provided by react-router-redux:
*/
export const getActiveArticle = createSelector(
getArticles,
createMatchSelector({ path: '/articles/:articleId' }), // https://github.com/ReactTraining/react-router/blob/master/packages/react-router-redux/modules/selectors.js
((articles, match) => ({
activeArticleId: articles[match.params.articleId] // assuming I don't need to do any null checks for this example…
})
);
export default ({ getState }) => next => action => {
const ret = next(action);
if (process.env.NODE_ENV !== 'production') {
window.state = getState();
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment