Last active
November 14, 2024 16:58
-
-
Save jslatts/1c5d4d46b6e5b0ac0e917fa3b6f7968f to your computer and use it in GitHub Desktop.
Utility to bind selectors to a slice of state. Helpful for keeping things DRY when colocating selectors and reducers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example of usage | |
import { selectors } from './rootReducer'; | |
import { selectors } from '../../reducers/rootReducer'; | |
const mapStateToProps = (state: State, ownProps: any) => ({ | |
theseObjects: selectors.getTheseObjects(state), | |
thoseObjects: selectors.getThoseObjects(state), | |
showSomethinginUi: selectors.getSomethingFromUiSelectors(state), | |
}); | |
... MORE CODE ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
// Helper to wrap a redux selector with a predetermined state slice function | |
'use strict'; | |
export const bindSelectors = (slicer: (any) => any, selectors: *) => { | |
const keys = Object.keys(selectors); | |
const boundMethods = {}; | |
keys.forEach(k => { | |
boundMethods[k] = fullState => selectors[k](slicer(fullState)); | |
}); | |
return boundMethods; | |
}; | |
export default bindSelectors; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example root reducer | |
import { bindSelectors } from './bindSelectors'; | |
import ui, { selectors as uiSelectors } from './ui'; | |
import theseObjects, { selectors as theseObjectSelectors } from './theseObjectsReducers'; | |
import thoseObjects, { selectors as thoseObjectSelectors } from './thoseObjectsReducers'; | |
// State shape looks like | |
const defaultState = { | |
ui, | |
theseObjects, | |
thoseObjects, | |
}; | |
... | |
REDUCER CODE GOES HERE | |
... | |
export default rootReducer; | |
// bindSelectors binds a state slice to the sub-reducer's selectors | |
export const selectors = { | |
...bindSelectors(state => state.theseObjects, theseObjectSelectors), | |
...bindSelectors(state => state.thoseObjects, thoseObjectSelectors), | |
...bindSelectors(state => state.ui, uiSelectors), | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example theseObjects reducer | |
... | |
REDUCER CODE GOES HERE | |
... | |
export default theseObjects; | |
// Seletors in this file do not have to worry about what the state of the whole state tree looks like | |
// They just operate within the same scope of their corresponding reducers | |
export const selectors = { | |
getFilteredObjects: (state: State) => state.filter(t => t.someFlag === true), | |
}; |
@jslatts the selectors
object that is exported from rootReducer.js
could introduce a subtle bug if two slices of state define selectors with the same name.
Instead of spreading all selectors for each slice into a single object, you might consider namespacing them
export const selectors = {
theseObjects: bindSelectors(state => state.theseObjects, theseObjectSelectors),
thoseObjects: bindSelectors(state => state.thoseObjects, thoseObjectSelectors),
ui: bindSelectors(state => state.ui, uiSelectors),
};
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you very much for this!
I tried to make a TypeScript version of this, but in redefining the selector functions when wrapping them in the object in the parent reducer it lost all type safety (and editor hinting with it) - there wasn't any way I could find to retain things like the number of parameters in the functions, the parameter types, etc once the selector functions were recreated.
So I ended up reversing the direction - instead of the parent reducer doing the wrapping, the child reducer is given a slicer function by the parent that will give the child the right slice of the parent's state. The child reducer then defines its selectors in terms of the parent's state based on that slicer function.
I've written up the process and created a sample set of reducers demonstrating how it works when there are multiple depths at https://gitlab.com/dawnmist/redux-typescript-selectors