Reputation: 8655
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
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
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
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