Stiño
Stiño

Reputation: 2853

ReactJs: How to wait for componentDidMount() to finish before rendering?

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

Answers (5)

linasmnew
linasmnew

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

Kaivosukeltaja
Kaivosukeltaja

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

Davide Ungari
Davide Ungari

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

ShacharW
ShacharW

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

larv
larv

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

Related Questions