Skip to content

RFC: I've created a variant of connect() that is a tailored for using with reselect #405

@jimbolla

Description

@jimbolla

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions