Passing Asynchronously Acquired Data To Child Props
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>
);
}
});
Answer
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>
);
}
});
Related Questions
- → How to update data attribute on Ajax complete
- → October CMS - Radio Button Ajax Click Twice in a Row Causes Content to disappear
- → Octobercms Component Unique id (Twig & Javascript)
- → Passing a JS var from AJAX response to Twig
- → Laravel {!! Form::open() !!} doesn't work within AngularJS
- → DropzoneJS & Laravel - Output form validation errors
- → Import statement and Babel
- → Uncaught TypeError: Cannot read property '__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' of undefined
- → React-router: Passing props to children
- → ListView.DataSource looping data for React Native
- → Can't test submit handler in React component
- → React + Flux - How to avoid global variable
- → Webpack, React & Babel, not rendering DOM