49

In my componentDidMount() I am making an API call to fetch some data, this call then sets a state object that I use in my render.

componentDidMount() {
            const { actions } = this.props;

            this.increase = this.increase.bind(this);

            // api call from the saga
            actions.surveyAnswersRequest();

            // set breadcrumb
            actions.setBreadcrumb([{ title: 'Score' }]);
            actions.setTitle('Score');
            this.increase();
        }

In my render function I pass some prop values onto the view file:

render() {
        const { global, gallery, survey_answers, survey, survey_actual_answers } = this.props;

        if (global.isFetching) {
            return <Loading />;
        }
        return this.view({ gallery, survey_answers, survey, survey_actual_answers });
    }

The problem I am having is that the survey_actual_answers prop is not being set the first time that the page is loaded, however when I refresh the page the prop returns the data fine and the rest of the script will run. It's only the first time that it returns an empty array for that prop value.

This is how I have passed my props in:

Score.propTypes = {
    actions: PropTypes.object.isRequired,
    global: PropTypes.object.isRequired,
    survey: PropTypes.object.isRequired,
    survey_answers: PropTypes.object.isRequired,
    gallery: PropTypes.object.isRequired,
    survey_actual_answers: PropTypes.array.isRequired,
    survey_score_system: PropTypes.array.isRequired,
    survey_styles: PropTypes.object.isRequired,
    survey_general_doc_data: PropTypes.object.isRequired
};

function mapStateToProps(state, ownProps) {
    return {
        ...ownProps,
        global: state.global,
        gallery: state.gallery,
        survey: state.survey,
        survey_actual_answers: state.survey.survey_actual_answers,
        survey_answers: state.survey.survey_answers,
        survey_score_system: state.survey.survey_score_system,
        survey_styles: state.survey.survey_styles,
        survey_general_doc_data: state.survey.survey_general_doc_data,
        isFetching: state.isFetching
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators({
            ...globalActions,
            ...galleryActions,
            ...surveyActions
        }, dispatch)
    };
}

Does anyone know why this is happening? It's almost as if it's not calling componentDidMount at all.

user3574492
  • 6,225
  • 9
  • 52
  • 105
  • 14
    `componentWillMount()` happens _before_ `render()`. `componentDidMount()` happens _after_. https://facebook.github.io/react/docs/react-component.html#componentwillmount – jered Jul 26 '17 at 20:53
  • also. just set the isFetching as a direct prop rather than global object. – Dimitar Christoff Jul 26 '17 at 20:55
  • @jered I've tried putting the `actions.surveyAnswersRequest();` in `componentWillMount()` and still get the same problem – user3574492 Jul 26 '17 at 21:00
  • 2
    render will always be called before your ajax request comes back. write some code to handle the pending state – azium Jul 26 '17 at 21:40
  • @azium I already have, that's what `if (global.isFetching) ...` is doing – user3574492 Jul 26 '17 at 22:11
  • 1
    It's kind of hard to tell what your exact problem is. can you include some errors or console logs with unexpected behaviour? – azium Jul 26 '17 at 22:44
  • For some reason I always assumed `componentDidMount` got called before `render`, even though the docs state otherwise. This question may look silly in hindsight, but it's understandable. – Andrew Grimm Feb 06 '18 at 02:44

3 Answers3

78

This is happening because of how React works fundamentally. React is supposed to feel fast, fluent and snappy; the application should never get clogged up with http requests or asynchronous code. The answer is to use the lifecycle methods to control the DOM.

What does it mean when a component mounts?

It might be helpful to understand some of the React vocabularies a little better. When a component is mounted it is being inserted into the DOM. This is when a constructor is called. componentWillMount is pretty much synonymous with a constructor and is invoked around the same time. componentDidMount will only be called once after the first render.

componentWillMount --> render --> componentDidMount

How is that different than rerendering or updating?

Now that the component is in the DOM, you want to change the data that is displayed. When calling setState or passing down new props from the parent component a component update will occur.

componentWillRecieveProps --> shouldComponentUpdate-->componentWillUpdate
-->render-->componentDidUpdate

It is also good to note that http requests are usually done in componentDidMount and componentDidUpdate since these are places that we can trigger a rerender with setState.

So how do I get the data before the render occurs?

Well, there are a couple of ways that people take care of this. The first one would be to set an initial state in your component that will ensure that if the data from the http request has not arrived yet, it will not break your application. It will use a default or empty state until the http request has finished.

I usually don't like to have a loading modal, but sometimes it is necessary. For instance, when a user logs in you don't want to take them to a protected area of your site until they are finished authenticating. What I try to do is use that loading modal when a user logs in to front load as much data as I possibly can without affecting the user experience.

You can also make a component appear as loading while not affecting the user experience on the rest of the site. One of my favorite examples is the Airbnb website. Notice that the majority of the site can be used, you can scroll, click links, but the area under 'experiences' is in a loading state. This is the correct way to use React and is the reason why setState and HTTP requests are done in componentDidMount/componentDidUpdate.

EJ Mason
  • 2,000
  • 15
  • 15
  • 3
    Really awesome answer - this led me to the React.Component docs. In 2021, componentWillMount() is considered unsafe. But the important part is that the render happens before componentDidMount! – Michael Jay Jan 06 '21 at 22:22
  • 1
    @MichaelJay 100% correct! I mostly just wanted to point out all of the different lifecycle methods and when they occur. 99% of the time you will only care about the `componentDidMount` lifecycle method. – EJ Mason Sep 12 '22 at 14:59
4

Using setState in componentdidmount. This my code:

 async componentDidMount() {

    danhSachMon = await this.getDanhSachMon();
    danhSachMon=JSON.parse(danhSachMon);
    this.setState(danhSachMon);
}

render() {
    return (
        <View>
            <FlatList
                data={danhSachMon}
                showsVerticalScrollIndicator={false}
                renderItem={({ item }) =>
                    <View >
                        <Text>{item.title}</Text>
                    </View>
                }
                keyExtractor={(item, index) => index.toString()}
            />
        </View>
    )
}
truong luu
  • 392
  • 1
  • 14
3

componentWillMount is deprecated. Now you need to use "DidMount" and as soon as it finishes and changes the DOM on render, react will handle everything else.

Make sure you update and use the correct variables/state/props in the render.

componentDidMount() {    
    const { applicationId } = this.props;
    if (applicationId) {
      ApplicationService.getQuotesByApplicationId(applicationId).then((response) => {
        const quotes = _.get(response, 'data.data');
        this.setState({
          quotes,
        });
      });

    ....

    }

    render() {

        const quotes = _.get(this.state, 'quotes', null);
        return (
          <div className="application">
            <h4 className="application__sub">Application</h4>
            <h1 className="application__title">Quote Comparison</h1>
            <div className="form-wrapper">
              {this.renderQuotes(quotes)}
            </div>
            <div />
          </div>
        );
      }

Here I get the quotes from the API and as soon as it finishes it set a variable in the state, then the render and react do their work.

EduLopez
  • 721
  • 7
  • 18