Skip to content

Instantly share code, notes, and snippets.

@bstro
Last active May 21, 2018 12:45
Show Gist options
  • Select an option

  • Save bstro/2366fc8fae5c99803f942baaef161aac to your computer and use it in GitHub Desktop.

Select an option

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. 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 will not.
A potential solution that i've used often is to store route params in
Redux, so we could compute `activeArticle` using a selector written 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