Skip to content

Create ReduxConnection component #920

Closed
@Sawtaytoes

Description

@Sawtaytoes

I'd like to get away from the HoC connect method and instead utilize render props. While higher-order component functions are great if you're composing components in an export, I'd rather use JSX components to compose components instead. This allows for more freedom in how components are rendered and allows for the creation of dumb components that can dynamically listen to various Redux state props as-needed.

I've been working on my own component to do just that called ReduxConnection, but it's one of those things that probably should be maintained by the react-redux project. Digging through the source code, I found something similar, a Connect component that exists in connectAdvanced. If that was exposed somehow, wouldn't it allow for the same concept I'm referencing?

This is the current ReduxConnection component as I've got it:

const ReduxConnection = ({
  children,
  component,
  mapDispatchToProps = null,
  mapStateToProps = null,
  render,
  ...props
}) => (
  React.createElement(
    connect(mapStateToProps, mapDispatchToProps)(
      (component || render || children || throwError)
    ),
    props
  )
)

Unlike React Router's Route implementation, I can't manage the different rendering methods for component, render, or children. I personally like the distinction used by React Router and think it's a good template for other components with render props. This particular ReduxConnection component sends components to the connect HoC regardless of how those props should be handled.

The worst part is I have to be explicit in which props I'm accepting. Right now, I've only got it setup with mapStateToProps and mapStateToDispatch, but theoretically, it should support all function methods.

An example usage where this would be beneficial:

// `component`
<ReduxConnection
  namespace="someNamespace"
  mapStateToProps={loadingStateSelector}
  component={LoadingIndicator}
/>

// `render`
<ReduxConnection
  namespace="someOtherNamespace"
  mapStateToProps={loadingStateSelector}
  render={({ isLoading }) => (
    <LoadingIndicator isLoading={isLoading}>
      <div>Some Content</div>
    </LoadingIndicator>
  )}
/>

I can put these two components side-by-side or in the same parent component without the parent component needing to know any of its children's state props. This also allows LoadingIndicator to be stateless, and only stateful when required.

The ReduxConnection component also solves this issue of exporting both the stateless, unit-testable component and the connect-wrapped version like so:

// I dislike doing this but need to for unit testing.
export LoadingIndicator () => <div />
export default connect(state => state)(LoadingIndicator)

While this doesn't solve every unit-testing use case, it explicitly defines which props components are actually receiving versus ones they're grabbing from the ReduxConnection render prop. With connect, you have to mix in a bunch of extra props in your PropTypes which connect is passing in.

Another benefit is the ability to compose it like this:

const LoadingState = props => (
  <ReduxConnection
    {...props}
    mapStateToProps={loadingStateSelector}
  />
)

const LoadingInformation = () => (
  <Fragment>
    <LoadingState
      component={LoadingIndicator}
      namespace="someNamespace"
    />
    <LoadingState
      component={SpecialLoadingIndicator}
      namespace="someOtherNamespace"
    />
  </Fragment>
)

Lastly, if you've used React Hot Loader, you've probably run into higher-order component hot-reload issues before. The way I've gotten around that is using the extract-hoc/babel plugin in my Babel plugins. Not ideal. It's a lot easier to just use a React component and bypass this hack entirely.

Have I shown enough of a case for the addition of a render props version of connect or are there still unanswered questions?

References:

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