I am making an app that gets an array of news items from a remote source and displays them on a page.
I have the endpoint, and can make a successful call, proven by console logs, using $.getJSON()
. I placed this call into the parent component, because child components will need to use the data.
However, when I pass this data down to a child component, the console error appears:
Uncaught TypeError: Cannot read property 'headline' of undefined
This is because React is trying to render the component even before the data has been passed into it. This makes me think I should first be calling my ajax somewhere other than componentDidMount
.
To get around it, I set up a method on the child component that returns the headline if the prop is present:
getHeadline: function () {
if(this.props.newsItems){
return this.props.newsItems.headline
} else {
return null
}
},
This feels like a bit of a nasty way around it. Is there a better way, or am I missing something in my code?
var BigStory = React.createClass({
getHeadline: function () {
if(this.props.newsItems){
return this.props.newsItems.headline
} else {
return null
}
},
render: function () {
console.log('props:', this.props);
console.log('newsItems:', this.props.newsItems);
return (
<div className="big-story col-xs-12">
<div className="col-sm-5">
<h1>{this.getHeadline()}</h1>
<p>Placeholder text here for now.</p>
<p>time | link</p>
</div>
<div className="col-sm-7">
<img src="http://placehold.it/320x220" alt=""/>
</div>
</div>
);
}
});
var Main = React.createClass({
getInitialState: function () {
return {
newsItems: []
}
},
componentDidMount: function () {
this.getNewsItems();
},
getNewsItems: function () {
$.getJSON('http://www.freecodecamp.com/news/hot', (data) => {
console.log('data sample:', data[0]);
this.setState({newsItems: data})
})
},
render: function () {
return (
<div className="container">
<div className="main-content col-sm-12">
<div className="left-sided-lg-top-otherwise col-lg-8 col-md-12 col-sm-12 col-xs-12">
<BigStory newsItems={this.state.newsItems[0]}/>
</div>
</div>
</div>
);
}
});
I would suggest leaving it up to the parent to decide what to do when it is in a "loading" state and leaving BigStory
as a "dumb" component that always renders the same assuming it will always receive a valid newsItem
.
In this example, I show a <LoadingComponent />
, but this could be whatever you need it to be. The concept is that BigStory
shouldn't have to worry about edge cases with "receiving invalid data".
var Main = React.createClass({
// ...
render() {
const {newsItems} = this.state;
// You could do this, pass down `loading` explicitly, or maintain in state
const loading = newsItems.length === 0;
return (
<div className="container">
<div className="main-content col-sm-12">
<div className="left-sided-lg-top-otherwise col-lg-8 col-md-12 col-sm-12 col-xs-12">
{loading
? <LoadingComponent />
: <BigStory newsItem={newsItems[0]} />
}
</div>
</div>
</div>
);
}
});
function BigStory(props) {
// Render as usual. This will only be used/rendered w/ a valid
return (
<div className="big-story col-xs-12">
<h1>{props.headline}</h1>
{/* ... */}
</div>
)
}
An alternative solution (although I recommend an approach more like above) would be to always make use of the BigStory
component in the same way, but provide it a "placeholder story" when there are no stories loaded.
const placeholderNewsItem = {
headline: 'Loading...',
/* ... */
};
var Main = React.createClass({
// ...
render() {
const {newsItems} = this.state;
// Conditionally pass BigStory a "placeholder" news item (i.e. with headline = 'Loading...')
const newsItem = newsItems.length === 0
? placeholderNewsItem
: newsItems[0];
return (
<div className="container">
<div className="main-content col-sm-12">
<div className="left-sided-lg-top-otherwise col-lg-8 col-md-12 col-sm-12 col-xs-12">
<BigStory newsItem={newsItem} />
</div>
</div>
</div>
);
}
});
This is a pretty common scenario. John Carpenter's approach would work. Another approach that I use often would be to create some type of Loading
component with a spinner image (or whatever else you might use to signify that data is on its way). Then, while the client waits for the data to arrive, you can render that Loading
component. For example:
render: function () {
if (this.state.newsItems.length === 0) return <Loading />;
return (
<div className="container">
<div className="main-content col-sm-12">
<div className="left-sided-lg-top-otherwise col-lg-8 col-md-12 col-sm-12 col-xs-12">
<BigStory newsItems={this.state.newsItems[0]}/>
</div>
</div>
</div>
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With