-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
I wasn't quite happy with the API for connect()
so I went about recreating my own. A few design goals:
- Simpler API. Replace the 3 functions with 1, by having access to state, props, and dispatch all at once.
- Make the API reselect friendly, and use it to cache results
- Be able to partially apply state/props as args of an action creator
An example of starting with no reselect
selectors and building up from there:
Version 1: this works, but the onLoad arrow function will be recreated everytime props/state change:
connectToStore(() => (state, props, dispatch) => ({
Branding: customization.signIn[state.account.signIn.customSignIn] || MainBranding,
loaded: state.account.signIn.loaded,
onLoad: () => dispatch(actionCreators.getSignInConfig(props.location)),
}))(SignIn);
Version 2: uses reselect's createSelector
to solve the function issue, but gets verbose:
connectToStore(() => createSelector(
state => customization.signIn[state.account.signIn.customSignIn] || MainBranding,
state => state.account.signIn.loaded,
createSelector(
(_, __, dispatch) => dispatch,
(_, props) => props.location,
(dispatch, location) => () => dispatch(actionCreators.getSignInConfig(location)),
),
(Branding, loaded, onLoad) => ({ Branding, loaded, onLoad })
))(SignIn);
Version 3: this version users createStructuedSelector to make it less verbose:
connectToStore(() => createStructuredSelector({
Branding: state => customization.signIn[state.account.signIn.customSignIn] || MainBranding,
loaded: state => state.account.signIn.loaded,
onLoad: createSelector(
(_, __, dispatch) => dispatch,
(_, props) => props.location,
(dispatch, location) => () => dispatch(actionCreators.getSignInConfig(location)),
),
}))(SignIn);
Version 4: utilize dispatchable
to make the actionCreator call less verbose:
connectToStore(() => createStructuredSelector({
Branding: state => customization.signIn[state.account.signIn.customSignIn] || MainBranding,
loaded: state => state.account.signIn.loaded,
onLoad: dispatchable(actionCreators.getSignInConfig, (_, props) => props.location),
}))(SignIn);
Version 5: make createStructuredSelector implicit if a plain object is returned instead of a function
connectToStore(() => ({
Branding: state => customization.signIn[state.account.signIn.customSignIn] || MainBranding,
loaded: state => state.account.signIn.loaded,
onLoad: dispatchable(actionCreators.getSignInConfig, (_, props) => props.location),
}))(SignIn);
Current source with some explanatory comments is here
The main exports are connectToStore(selectorFactory, options)
and dispatchable(actionCreator, ...selectorsToPartiallyApply)
. connectToStore
is the variant of react-redux's connect()
while dispatchable
wraps action creators in a selector that binds it to dispatch.
The signature for selectorFactory is () => (state, props, dispatch) => {newProps}
. Any reselect
selectors, including those created by dispatchable
created inside the factory function are scoped to that component instance.
selectorFactory
has a shorthand that I use 99% of the time: () => {inputSelectors}
, where inputSelectors
is passed to createStructuredSelector to create the selector.
I think it's coming along nicely, although I don't yet have hot reloading support (I'm not sure what that will entail) or any argument validation. Any constructive criticism would be welcome.