Reputation: 2853
How to wait for async componentDidMount() to finish before rendering?
My app.jsx:
constructor(props) {
super(props);
this.state = {
loggedInUser: null,
isAuthenticated: false,
isAuthenticating: true
};
}
componentDidMount() {
try {
var user = authUser();
console.log('User: ' + user)
if (user) {
console.log('Is logged in: ' + this.state.loggedInUser)
this.userHasAuthenticated(true);
}
}
catch(e) {
alert(e);
}
this.setState({ isAuthenticating: false });
}
render() {
console.log('in render: ' + this.state.loggedInUser)
// Should execute **after** authUser() in componentDidMount has finished
...
}
componentDidMount calls this async function:
function authUser() {
firebase.auth().onAuthStateChanged(function(user) {
return user
})
}
console.log('in render: ' + this.state.loggedInUser)
How can I make the render method wait for authUser() in componentDidMount?
Upvotes: 15
Views: 31126
Reputation: 3977
Don't wait for componentDidMount
to finish before rendering, that would be a misuse of the library, wait for your authUser
to finish.
You can do that by utilising your isAuthenticating
state property in combination with promises
.
function authUser() {
return new Promise(function (resolve, reject) {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
resolve(user);
} else {
reject('User not logged in');
}
});
});
}
You could use your existing isAuthenticating
flag as follows:
componentDidMount() {
authUser().then((user) => {
this.userHasAuthenticated(true);
this.setState({ isAuthenticating: false });
}, (error) => {
this.setState({ isAuthenticating: false });
alert(e);
});
}
Then inside render:
render() {
if (this.state.isAuthenticating) return null;
...
}
This will prevent your component from being added to the DOM until your authUser
function completes.
Upvotes: 28
Reputation: 15735
Your authUser()
function doesn't seem to be set up correctly. You're returning the user object in the callback, but the function itself is not returning anything so var user = authUser();
will always return undefined
.
You'll need to change authUser()
to either call a callback function or return a Promise
that resolves when the user is returned from Firebase. Then set the authentication status to your state once the promise is resolved or the callback is executed. In your render()
function return null
if the authentication has not yet finished.
Async function with callback:
function authUser(callback) {
firebase.auth().onAuthStateChanged(function(user) {
callback(user);
})
}
Using the callback with your component:
componentDidMount() {
try {
authUser(function(user) {
console.log('User: ' + user)
if (user) {
console.log('Is logged in: ' + this.state.loggedInUser)
this.userHasAuthenticated(true);
this.setState({ isAuthenticating: false });
}
});
}
catch(e) {
alert(e);
}
}
render() {
console.log('in render: ' + this.state.loggedInUser)
if (this.state.isAuthenticating === true) {
return null;
}
// Rest of component rendering here
}
Upvotes: 2
Reputation: 1960
I think here the problem is that authUser
is async. I would use promises in order to handle in a clean way the async response. I do not know firebase API but apparently support promises: https://firebase.google.com/docs/functions/terminate-functions
Otherwise you could use a library like bluebird and modify your authUser function to return a promise: http://bluebirdjs.com/docs/working-with-callbacks.html
If your are not familiar with promises, you should first of all read about the fundamentals: https://bitsofco.de/javascript-promises-101/
Upvotes: -1
Reputation: 207
If you want the component to not being rendered, wrap your component with some custom authorization component and don't render your component if the user is not logged in.
It's bad practice to try preventing the render
function to call.
Upvotes: 0
Reputation: 938
componentDidMount
will always fire after the first render.
either use componentWillMount
or live with the second render, setState
triggers a new render and componentWillMount always fires after the component did mount, i.e it rendered correctly.
Upvotes: 1