How can I block a React component to be rendered until I fetched all informations?
Here's what I do normally:
class Login extends React.Component {
constructor(props) {
//IMPLEMENT OTHER JUNK HERE
this.state = {
data: null //This is what our data will eventually be loaded into
};
}
componentDidMount() {
this.loadData();
}
loadData() {
/*LOAD DATA, INSERT BELOW LINE IN CALLBACK FUNCTION
this.setState({
data: //LOADED DATA
});
*/
}
render() {
if (!this.state.data) {
return <div />
}
//WE HAVE DATA, DO A NORMAL RENDER
return (
<div id="login-page">
<div className="container-fluid">
<div className="row">
<div className="col-md-2">
<Link to="/" className="home-link"><img src={BASE_URL + '/assets/img/logo.svg'} alt="Logo" /></Link>
</div>
</div>
<div className="row">
<div className="col-lg-4 col-lg-offset-4">
<h1><FormattedMessage {...messages.loginPageTitle} /></h1>
</div>
</div>
{React.cloneElement(this.props.children || <div />, { onSubmit: this.handleFormSubmit, login: this.props.login })}
</div>
</div>
);
}
}
Here's a breakdown of what is going to happen...
- Component is going to load
- componentDidMount() fires, runs loadData()
- loadData() starts ajax request, returns before ajax request returns data because we love asynchronous data loads
- render() runs. Since
this.state.data
isnull
, we have pass into the if block, and<div />
is returned. - Ajax data load finishes, and a
this.setState()
call is made, which forces a re-render. - render() runs again. Since
this.state.data
contains a value now, we skip over the if block and render our normal stuff.
Edit (11 Oct 2019): Migrated componentWillMount() to componentDidMount()
Always let React render.
While you're doing something asynchronous, show a loading spinner or something.
render() {
<div>
{ this.state.isLoading &&
<div>Loading.. please wait!</div>
}
{ !this.state.isLoading &&
<div>My data has arrived!</div>
}
</div>
}
An alternative way to the accepted answer using the constructor. Personally I find this a little cleaner.
class Menu extends Component {
state = {}
constructor(props) {
super(props)
loadData().then(data =>
this.setState({data: data})
)
}
async loadData() {
//get your data
}
render() {
if (isEmpty(this.state)) {
return <div>Loading</div>
}
return (
<div id="site">
{data}
</div>
)
}
You can try something like
/** Page Login */
class Login extends React.Component {
constructor(props) {
...
this.state = {
ready: false
};
}
componentWillMount() {
setTimeout(this.handleLoading, 10000);
}
handleLoading() {
this.setState({ ready: true });
}
render() {
if(!this.state.ready)
return null;
return normalRender();
}
}
Suspending the render seems hacky...
Why not render a part of your component with some placeholder-sub-component.. and then, when the ajax call finishes, fire an action to change the state and render your original component.
It'll be better both in terms of UX and elegance.