Source:  Twitter logo

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?

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.

27 users liked answer #0dislike answer #027
linasmnew profile pic
linasmnew

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
}
2 users liked answer #1dislike answer #12
Kaivosukeltaja profile pic
Kaivosukeltaja

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.

1 users liked answer #2dislike answer #21
Lucas Reppe Welander profile pic
Lucas Reppe Welander

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.

0 users liked answer #3dislike answer #30
ShacharW profile pic
ShacharW

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/

-1 users liked answer #4dislike answer #4-1
Davide Ungari profile pic
Davide Ungari

Copyright © 2022 QueryThreads

All content on Query Threads is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).