Chris Hansen
Chris Hansen

Reputation: 8655

setState not being triggered

I have the following code

 setTimeout(() => {
        this.setState({loading: true});
    }, 0);
   axos.post("....")

setState is not being triggered for some reason. Can someone please help?

Here's the code

submit(ev) {
    ev.preventDefault();

    setTimeout(() => {
        this.setState({loading: true});
    }, 0);

    const {email, password} = ev.target.elements;

  app.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
    // As httpOnly cookies are to be used, do not persist any state client side.
    app.auth().signInWithEmailAndPassword(email, password).then(user => {
        // Get the user's ID token as it is needed to exchange for a session cookie.
        app.auth().onAuthStateChanged((user) => {
            if (user) {
                this.setState({loading: true});
                return user.getIdToken().then(idToken => {
                    // Session login endpoint is queried and the session cookie is set.
                    // CSRF protection should be taken into account.
                    const csrfToken = Utils.getCookie('csrfToken');
                    return this.postIdTokenToSessionLogin('/auth/session-login', idToken, csrfToken, "auth");
                });
            } else {
                this.setState({error: "There was an error", loading: false});
            }
        });
    }).catch(error => {
        this.setState({error: error.message, loading: false});
    })
}

Upvotes: 0

Views: 111

Answers (3)

Willman.Codes
Willman.Codes

Reputation: 1413

As mentioned you need to either bind the submit in the constructor, define it as an arrow function, or call it inline with a callback arrow function.

Defining as an arrow function the is often the 'easiest' way but all will work in this case

Change:

submit(ev) {
    /* submit code*/
}

To:

submit = (ev) =>{
    /* submit code*/
}

Then you can use by simply adding as a prop such as to a button:

<button onClick={this.onSubmit}>Click Me</button>

Or inline arrow function:

<button onClick={(ev)=>this.onSubmit(ev)}>Click Me</button>

Additionally:

You need to update loading state once you have a user:

if (user) {
    this.setState({loading: false});
    //...rest of code
}

Keep in mind that neither setTimeout nor setState are applied directly but go into the queue (You are adding the setTimeout to the queue and when the setTimeout gets called you are then adding the setState to the queue further delaying the setState).

If you need to ensure that code happens after a setState call you should provide a callback function:

setState((prevState)=>{
   return {someState}
},()=>{
   //code to execute after setState is executed
})

In my Opinion: you may instead want to initialize your user state to be:

state={
   user:{notLoaded:true}
}

Then in your auth flow use setState({user}) if there is a user, setState({user:null}) when no user is found, and setState({user:{error}) if there is an error.

This reduces the complexity of state management without the need for separate error/loading states and then in your render function:

if(!user)
    return <LoginComponent/>
if(user.notLoaded)
    return <LoadingComponent/>
if(user.error)
    return <ErrorComponent/>

//we have a user!
return <UserComponent />

Upvotes: 0

Dupocas
Dupocas

Reputation: 21317

When using class methods we should bind them in constructor first. This has to do with how this works in javascript. When accessing this from inside submit it actually refers to submit and not to your Component. Either bind like this

class Foo extends React.Component{
    constructor(props){
        super(props)
        this.submit = this.submit.bind(this)
    }

    submit(e){
        //now this refers to Foo's instance
    }
}

Or you can use arrow functions which have a lexical this

class Foo extends React.Component{
    submit = e =>{
        //here this already refers to Foo's instance
    }
}

Upvotes: 2

Hamza El Aoutar
Hamza El Aoutar

Reputation: 5657

setTimeout(() => {
  this.setState({loading: true});
}, 0);

Calling setTimeout with 0 doesn't mean that the callback is processed immediately.

The callback won't be processed until the call stack is empty.

The following code illustrates this: (you will first see the values from the loop, and only after you will see now)

setTimeout(function() { 
    console.log('now');
}, 0);

for (i = 0; i < 10; i++) {
    console.log(i); 
}

Upvotes: 1

Related Questions