You might need to understand javascript async behavior better. Async means "don't wait". That the task will happen in the background and other code will continue to execute. A good way to manage this is to set state on your component. For example, when you enter componentDidMount
set a loading
state to true
. Then when your async function completes, set that state to false
. In your render
function you can then either display a "loading..." message or the data.
Here is some code that shows a simplified example of fetching data async and how you could handle that in React. Open the developer tools in your browser and look at the console output to understand the React lifecycle better.
EDIT: Code has been updated to use the new React Lifecycle recommendations as of April 2018. In summary, I replaced componentWillMount
with the safer componentDidMount
.
It might seem inefficient to update the state after the component has already mounted, as 'componentDIDmount' correctly implies. However, per the official React documentation on componentDidMount:
"If you need to load data from a remote endpoint, this is a good place to instantiate the network request."
"Calling setState()
in this method will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render()
will be called twice in this case, the user won’t see the intermediate state."
Here's the complete example code:
class MyComponent extends React.Component {
constructor(props) {
super();
console.log('This happens 1st.');
this.state = {
loading: 'initial',
data: ''
};
}
loadData() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('This happens 6th (after 3 seconds).');
resolve('This is my data.');
}, 3000);
});
console.log('This happens 4th.');
return promise;
}
componentDidMount() {
console.log('This happens 3rd.');
this.setState({ loading: 'true' });
this.loadData()
.then((data) => {
console.log('This happens 7th.');
this.setState({
data: data,
loading: 'false'
});
});
}
render() {
if (this.state.loading === 'initial') {
console.log('This happens 2nd - after the class is constructed. You will not see this element because React is still computing changes to the DOM.');
return <h2>Intializing...</h2>;
}
if (this.state.loading === 'true') {
console.log('This happens 5th - when waiting for data.');
return <h2>Loading...</h2>;
}
console.log('This happens 8th - after I get data.');
return (
<div>
<p>Got some data!</p>
<p>{this.state.data}</p>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementsByClassName('root')[0]
);
And here is the working example on CodePen.
Finally, I think this image of the modern React lifecycle created by React maintainer Dan Abramov is helpful in visualizing what happens and when.
NOTE that as of of React 16.4, this lifecycle diagram has a small inaccuracy: getDerivedStateFromProps
is now also called after setState
as well as forceUpdate
. See this article from the official React blog about the Bugfix for getDerivedStateFromProps
This interactive version of the React lifecycle diagram created by Wojciech Maj allows you to select React version >16.04 with the latest behavior (still accurate as of React 16.8.6, March 27, 2019). Make sure you check the "Show less common lifecycles" option.