Skip to content
This repository has been archived by the owner on Sep 10, 2022. It is now read-only.

request: Add HOC to support "get async data on mount" pattern. #54

Closed
jimbolla opened this issue Nov 12, 2015 · 8 comments
Closed

request: Add HOC to support "get async data on mount" pattern. #54

jimbolla opened this issue Nov 12, 2015 · 8 comments

Comments

@jimbolla
Copy link

I have this pattern all over my codebase:

const ComponentA = React.createClass({

    propTypes: {
        id: PropTypes.string,
    },

    contextTypes,

    getInitialState() {
        return {};
    },

    componentDidMount() {
        http.get(this.context.rootUrl + "SomeAPI", {id: this.props.id})
            .then(config => {
                if (this.isMounted())
                    this.setState({data});
            });
    },

    render() {
        return this.state.data
            ? (<ComponentB {...this.props} {...this.state.data}/>)
            : (<Spinner/>);
    }
});

A component, such as ComponentA, has to make an ajax call on mount, and then pass that data to another component on completion. I'm imagining a HOC signature that looks something like this:

onMount(
    getData: (props: Object, context: Object) => Promise,
    BaseComponent: ReactElementType
): ReactElementType
@acdlite
Copy link
Owner

acdlite commented Nov 12, 2015

Thanks for the suggestion! I'm not sure to what extent Recompose should be providing lifecycle hooks. I'd like to think about this for a while before making any decisions.

@jimbolla
Copy link
Author

Hmmm. Well, one option would be to have a separate package recompose-lifecycle for stuff like that, if there's enough code to justify one. Anyways, here's my naive implementation of onMount:

const onMount = (getData, BaseComponent) => React.createClass({

    contextTypes: BaseComponent.contextTypes,

    getInitialState() {
        return {};
    },

    componentDidMount() {
        getData(this.props, this.context)
            .then(promise => {
                if (this.isMounted)
                    this.setState({promise});
            });
    },

    render() {
        return (
            <BaseComponent {...this.props} {...this.state.promise} />
        );
    },
});

@RafalFilipek
Copy link

Lifecycle events are cool but I think there is no reason for packages like recompose-lifecycle in that case. Quick example:

import React from 'react'
import { render } from 'react-dom'
import { compose, lifecycle, branch, renderNothing, withState, renderComponent } from 'recompose'

// returns HOC
const componentWithApi = (BaseComponent, requests) => (
  compose(
    // are we done?
    withState('loaded', 'setLoaded', false),
    lifecycle(
      (c) => (
        // Lets make all requests and then update state
        Promise.all(requests.map(r => r()))
        .then(() => c.props.setLoaded(true))
      ),
      () => {}
    ),
    // You can show some loader while data are loading or
    // render nothig.
    branch(
      ({ loaded }) => loaded,
      // BOOOM! Ready to go!
      renderComponent(BaseComponent),
      // loading ... loading .. loading
      renderComponent(() => <div>loader</div>),
    )
  )(renderNothing())
)
// Your complex application
const App = ({ loaded }) => <div>App { loaded ? 'yes' : 'no' }</div>
App.propTypes = { loaded: React.PropTypes.bool }

// Array of requests (set timeout mock)
const requests = [
  () => new Promise((resolve) => setTimeout(resolve, 1000)),
  () => new Promise((resolve) => setTimeout(resolve, 2000))
]
// You can wrap any component with componentWithApi function
const AppWithApi = componentWithApi(App, requests)
render(<AppWithApi />, document.querySelector('#app'))

@timmolendijk
Copy link

@RafalFilipek This breaks in universal app situations (server-side kick-off alongside client-side run-time).

The missing aspect here is that a lifecycle method such as componentDidMount is essential to some (many) data loading scenarios in universal apps, because it is invoked on client but not on server. As long as recompose doesn't offer some way of making this distinction, it won't enable me to get rid of the class notation.

@timmolendijk
Copy link

Btw, I'm currently using the following home-grown doOnMount function:

import { Component } from 'react';
import createHelper from 'recompose/createHelper';
import createElement from 'recompose/createElement';

export const doOnMount = createHelper(callback => BaseComponent =>
  class extends Component {
    componentDidMount() {
      callback(this.props);
    }
    render() {
      return createElement(BaseComponent, this.props);
    }
  }
, 'doOnMount');

Let's find out how it suits my purpose…

@wuct
Copy link
Contributor

wuct commented Apr 23, 2016

I am agree with @timmolendijk. componentDidMount is pretty useful and it's the reason why react-lifecycle-hoc exists. Currently, the only implementation in react-lifecycle-hoc is componentDidMount.

@wuct
Copy link
Contributor

wuct commented Apr 23, 2016

However, it seems like there is still no consensus whether recompose should provide lifecycle hooks. See #124.

@acdlite
Copy link
Owner

acdlite commented May 21, 2016

Closing because we now have two ways to do this: rx-recompose's mapPropsStream (which will soon be part of Recompose itself) and the lifecycle helper. Any other proposals should be brought up in a separate issue/PR. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants