Reputation: 2262
I have a React component hierarchy like this:
AgreementContentWidget
AgreementTitleContainer
AgreementTitle <-- this is ok, but
CommentList <-- problem here
SectionsContainer
Mounting AgreementContentWidget
:
<AgreementContentWidget agreement_id={85} />
requests the 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 pass agreement={this.state.agreement}
to <AgreementTitleContainer>
to render components comprising an agreement's title.
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>
takes {this.props.agreement}
and it's got contents of agreement 85. It renders and does other operations as expected. However, <CommentList>
has undefined
for commentable_id
as this.props.agreement.id
is undefined (via debugger: this.props.agreement
is always undefined for this component).
So:
{this.props.agreement}
defined for <AgreementTitle>
?<CommentList>
?I know that <CommentList>
works as expected in other contexts where a value is passed to commentable_id
(e.g. commentable_id={42}
) rather than from an object in this.state
(as above).
I noted that for <CommentList>
when it tries a GET
request 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 get an error message from failed 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 console.error
function then prints out:
/comments/?commentable_type=agreement&id=85 error Not Found
So at that point of execution, this.props.commentable_id
seems to be defined now.
I'm thinking that the decision for <AgreementContentWidget>
to:
GET
agreement 85this.state.agreement
with agreement 85 details <--this.state.agreement
as input to props, e.g. agreement={this.state.agreement}
has something to do with my issues.
Architecturally, I'd like to retain the GET
agreement at <AgreementContentWidget>
level and have the retrieved agreement details propagate down.
What am I missing?
Upvotes: 4
Views: 12077
Reputation: 7588
What you're initializing in getInitialState
is what's passed the first time <AgreementContentWidget>
renders. You think <AgreementTitle>
works properly because it is re-rendered when you get the agreement data from your backend, but CommentsList, being dependent on that ID to load its own data, will error out before it can render. It doesn't know to go out and get the data again because it's only loading in componentDidMount
.
You should defer the retrieval of comments until you get the correct property. To do this, use React's componentWillReceiveProps(nextProps)
function and wait until agreement.id
exists, and is not 0 before loading your ajax.
So REMOVE this.componentDidMount
from CommentList and ADD this:
this.componentWillReceiveProps(nextProps){
if(nextProps.agreement && nextProps.agreement.id){
this.loadComments();
}
}
You should also set propTypes so you can be alerted to these issues with nice messages and provide some self documentation to your code.
Upvotes: 4