Reputation: 1086
I need to fetch some information before rendering my component. The information will be provided by an API and fetched with an AJAX call.
I'm just trying to wait 10 seconds before rendering my component but it says:
Uncaught Invariant Violation: Login.render(): A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object.
Can I render my component after the fulfillment of a promise?
/** Page Login */
class Login extends React.Component {
/**
* @constructor
* @param {object} props La fonction super() appelle le parent pour y transmettre ses propriétés
*/
constructor(props) {
super(props);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
/**
* Reçoit les valeurs des formulaires
*/
handleFormSubmit(data) {
const { dispatch } = this.props;
dispatch(fetchLoginAuth(data));
}
normalRender() {
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>
);
}
/**
* Render le component - ReactTransitionGroup
* @return {JSX} Rend la page Registration
*/
render() {
setTimeout(this.normalRender, 10000);
}
}
I use ES6 with JSX, redux, an universal router with react-router.
Thank you very much for your help!
Upvotes: 56
Views: 93037
Reputation: 1189
Update: This answer is out of date now. You are better off moving away from classes and using hooks.
#######################
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>
)
}
Upvotes: 6
Reputation: 420
if you have to handle same case in multiple places or you don't have to use state just because to track async function callback in presentational components, there is an easy way to take out these problems please refer react-promise.
react-promise provide a hook called usePromise
, sandbox (you can play around)
import React from "react";
import usePromise from "react-promise";
import "./styles.css";
const prom = new Promise((resolutionFunc, rejectionFunc) => {
// call your async return values after succesfull response
setTimeout(function () {
resolutionFunc(<h1> Hello World </h1>);
}, 5000);
});
const App = () => {
const { value, loading } = usePromise(prom);
if (loading) return <div> loading </div>;
return <div>{value}</div>;
};
export default App;
you can see Hello world after some delay
Upvotes: 0
Reputation: 1200
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...
this.state.data
is null
, we have pass into the if block, and <div />
is returned.this.setState()
call is made, which forces a re-render.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()
Upvotes: 64
Reputation: 20614
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>
}
Upvotes: 12
Reputation: 470
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.
Upvotes: 0
Reputation: 1582
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();
}
}
Upvotes: 3