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?
source to share
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.
source to share