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 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 the browser's console.

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 * as selectors 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
// whenever a prop-based selector is composed with another.
export const getArticle => createSelector(
getArticleIdFromProps,
getArticles,
fp.get // lodash/fp makes point-free function application particularly easy here.
);
/*
The above 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 and exposed on window.selectors,
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) => articles[match.params.articleId]) // leaving out null checks for brevity/clarity in 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