heroku / react-refetch

A simple, declarative, and composable way to fetch data for React components
https://blog.heroku.com/react-refetch
Other
3.43k stars 140 forks source link

Add state-dependent mapping, not only props. #140

Open eyalw opened 8 years ago

eyalw commented 8 years ago

Scenario You have a Container component that has logic and data-fetching (refetch decorated component). The container parents many presentational components, like filter selection, and table view. The filters affect the data fetched from the server, and the table view displays the results.

Now since the hierarchy is this:

The connect mapping cannot use the filtersState to change the data fetching based on filter selection.

Suggested solution What I suggest is adding a "containerState" feature (or a better name) to React-Refetch. The feature will send "setContainerState()" function prop down to Container, and instead of the container holding state, the Connect will, and it will be able to use it for the mapping.

I'll attach the code I wrote to implement this, so we can discuss it.

ryanbrainard commented 8 years ago

Would you be able to instead put the presentation state in an anchor/hash in the URL and pass it down as props?

nfcampos commented 8 years ago

how about just using withState from https://github.com/acdlite/recompose/blob/master/docs/API.md#withstate ? Wouldn't this do what you're after @eyalw ? This is what I do in my components when i need something similar

compose(
  withState('containerState', 'setContainerState', null),
  connect(...)
)(MyComponent)
TimothyKrell commented 8 years ago

After watching this React Rally talk by @ryanflorence I've started experimenting with the child-as-a-function style for react-refetch instead of HOC. Using this pattern, this issue is not a problem.

// Create a wrapper component to facilitate the child as a function pattern
class Connector extends React.Component {
  static propTypes = {
    mappings: React.PropTypes.object.isRequired,
    children: React.PropTypes.func.isRequired,
  }

  render() {
    const { children, ...rest } = this.props
    return children(rest)
  }
}
Connector = apiConnect(({ mappings }) => ({
  ...mappings
}))(Connector)

// Component with state
class StatefulComponent extends React.Component {
  state = { userId: 1 }

  render() {
    return (
      <div>
        <UserSelector onSelect={id => this.setState({ userId: id })} />
        <Connector
          mappings={{
            user: `http://someapi.com/user/${this.state.userId}`
          }}
        >
          {({ user }) => {
            if (!user.fulfilled) return <Loading />

            return (
              <UserDetail user={user.value} />
            )
          }}
        </Connector>
      </div>
    )
  }
}

This may not be everyone's cup of tea, but it makes the state problem a non-issue. I'm liking it so far.