React: componentWillMount is called before render()?

Created on 15 Jun 2016  Â·  7Comments  Â·  Source: facebook/react

Hi together

Learning React at the moment and not sure if I'm not unterstanding a core part of React or this is a bug...

My idea was to create a simple login and don't show the login window if the user is already logged in. Authentication is handled via Firebase, so here's a small snippet of my code:

var Login = React.createClass ({

getInitialState: function(){
    return { loggedIn: false };
},

componentWillMount: function() {

    [ ... some firebase code ... ]

    firebase.auth().onAuthStateChanged(function(user) {
        if (user) {
            this.setState({ loggedIn: true });
        }
        else {
            this.setState({ loggedIn: false });
        }

}

render: function() {
    return (
      <div>
      {this.state.loggedIn ? (
        <div>
        <Backend/>
        </div>
      ) : (
         [ ... login form.... ]
      ) }
      </div>
    );
  }
}); // END LOGIN

So my problem is the following: componentWillMount should be called before render, so if the user is already logged in, the new state is loggedIn: true and so it only renders the backend and not the login form... but unfortunately this new state in componentWillMount is not taken before render and it actually renders the login form

Thanks for the answer, cheers!

Martin

Unconfirmed

Most helpful comment

Does onAuthStateChanged invoke the callback synchronously?

All 7 comments

Does onAuthStateChanged invoke the callback synchronously?

How can I control that?

You can try and render a <Loading /> component until firebase has resolved the user for the first time.

var Login = React.createClass ({

getInitialState: function(){
    return { 
      loggedIn: false,
      loaded: false
    };
},

componentWillMount: function() {

    [ ... some firebase code ... ]

    firebase.auth().onAuthStateChanged(function(user) {
        if (user) {
            this.setState({ loggedIn: true, loaded: true });
        }
        else {
            this.setState({ loggedIn: false, loaded: true });
        }

}

render: function() {
    if (!this.state.loaded) return <Loading />;
    return (
      <div>
      {this.state.loggedIn ? (
        <div>
        <Backend/>
        </div>
      ) : (
         [ ... login form.... ]
      ) }
      </div>
    );
  }
}); // END LOGIN

That way you don't show either screen until you've resolved the actual auth state for the user. Otherwise, you can't really control the firebase API to that extent, it's likely inherently asynchronous as it has to make an HTTP request before it resolves, which is almost always going to occur after your component renders.

Yeah, sounds like your firebase.auth.onAuthStateChanged(...) callback doesn't get called immediately (they likely launch a request to the firebase server and set the auth state when the server replies). Regardless, the data is not available at render time.

This is effectively a request for #6481 and #1739. Closing as a duplicate of those two issues.

The workaround suggested by @Aweary is currently what @sebmarkbage generally recommends.

Hmm, seems like code below this.sate({}) is completely ignored in my example… what could cause that?
Sorry for interrupting you guys… I'm completely new to React. Next time I'm posting first in StackOverflow… :)

@mnbucher setState shouldn't be completely ignored, but it may be asynchronous. There is a big red notice in the documentation (https://facebook.github.io/react/docs/component-api.html#setstate) that says:

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

Maybe that's what you're running into?

I have encountered the same problem, you should try this solution, it solved the problem for me.

componentWillMount: function() {

    [ ... some firebase code ... ]
    var _this = this
    firebase.auth().onAuthStateChanged(function(user) {
        if (user) {
            _this.setState({ loggedIn: true });
        }
        else {
            _this.setState({ loggedIn: false });
        }

}
Was this page helpful?
0 / 5 - 0 ratings