Unexpectedly repeating undefined state variable when trying to pass as props

I have a hierarchy of React components:

 AgreementContentWidget
   AgreementTitleContainer
     AgreementTitle <-- this is ok, but
     CommentList    <-- problem here
   SectionsContainer

      

Installation AgreementContentWidget

:

 <AgreementContentWidget agreement_id={85} />

      

requests a widget to load agreement 85.

var AgreementContentWidget = React.createClass({
  getInitialState: function() {
    return {agreement: []};
  },

  componentWillMount: function() {
    this.loadAgreementDetails();
  },

  loadAgreementDetails: function() {
    $.ajax({
      url: "/agreements/" + this.props.agreement_id, 
      type: 'GET',
      dataType: 'json',
      success: function(agreement) {
        this.setState({agreement: agreement.agreement});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error("/agreements/" + this.props.agreement_id, status, err.toString());
      }.bind(this)
    });
  },

  handleAgreementUpdate: function() {
    this.loadAgreementDetails();
  },

  render: function() {
    return (
      <div className="panel panel-default">
        <div className="panel-body" id={"container_agreement_" + this.state.agreement.id}>
          <div className="col-lg-12">           
            <AgreementTitleContainer agreement={this.state.agreement} onAgreementUpdate={this.handleAgreementUpdate} />
            <SectionsContainer agreement={this.state.agreement} />
          </div>
        </div>
      </div>
    );
  }
});

      

I am passing agreement={this.state.agreement}

in <AgreementTitleContainer>

to display the components that contain the header of the agreement.

Inside <AgreementTitleContainer>

:

var AgreementTitleContainer = React.createClass({
  handleAgreementUpdate: function() {
    this.props.onAgreementUpdate();
  },

  render: function() {
    return (
      <div className="row" id="container_title">
        <AgreementTitle agreement={this.props.agreement} onAgreementUpdate={this.handleAgreementUpdate} />
        <CommentList commentable_type={"agreement"} commentable_id={this.props.agreement.id} />
        <br />
      </div>
    );
  }
});

      

<AgreementTitle>

accepts {this.props.agreement}

and it has received the content of agreement 85. It displays and performs other operations as expected. However, it <CommentList>

has undefined

for commentable_id

as this.props.agreement.id

- undefined
(via debugger: this.props.agreement

always undefined for this component).

So:

  • Why is it {this.props.agreement}

    designated for <AgreementTitle>

    ?
  • Why is it undefined for <CommentList>

    ?

I know it <CommentList>

works as expected in other contexts where value is passed to commentable_id

(for example commentable_id={42}

) rather than from object to this.state

(as above).

I noticed that for <CommentList>

when he tries the request GET

in loadComments()

below:

var CommentList = React.createClass({
  getInitialState: function() {
    return {comments: []};
  },

  componentDidMount: function() {
    this.loadComments();
  },

  loadComments: function() {
    $.ajax({
      url: "/comments/?commentable_type=" + this.props.commentable_type + "&id=" + this.props.commentable_id, 
      type: 'GET',
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments.comments});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error("/comments/?commentable_type=" + this.props.commentable_type + "&id=" + this.props.commentable_id, status, err.toString());
      }.bind(this)
    });
  },
  ...
  ...
}

      

I got an error with the error GET

GET https://tinya-thedanielmay-2.c9.io/comments/?commentable_type=agreement&id=undefined 404 Not Found

      

which I expect as id

(from this.props.commentable_id

above) is undefined

. However, the function console.error

outputs:

/comments/?commentable_type=agreement&id=85 error Not Found

      

So at this point of execution is this.props.commentable_id

now defined.

I think the solution for <AgreementContentWidget>

:

  • GET

    agreement 85
  • Ask this.state.agreement

    with agreement 85 details <-
  • Then go to use this.state.agreement

    as login credentials, eg.agreement={this.state.agreement}

has something to do with my problems.

Architecturally, I would like to keep the agreement GET

level <AgreementContentWidget>

and get the information about the reconstructed agreement down.

What am I missing?

+3


source to share


1 answer


What you initialize in getInitialState

is what was executed the first time <AgreementContentWidget>

. You think it <AgreementTitle>

works as expected because it re-renders when you get the agreement data from your backend, but the CommentsList relying on that id to load its own data will fail before it can render. It doesn't know how to get out and get the data again because it only loads into componentDidMount

.

You should postpone looking for comments until you get the correct property. To do this, use a React function componentWillReceiveProps(nextProps)

and wait until it agreement.id

exists and is not 0 before the ajax load.

So REMOVE this.componentDidMount

from CommentList and ADD:



this.componentWillReceiveProps(nextProps){
  if(nextProps.agreement && nextProps.agreement.id){
    this.loadComments();
  }
}

      

You should also set propTypes so that you can be alerted to these issues with nice messages and provide some self-help documentation for your code.

+4


source







All Articles