Can I explicitly call this.setState() in the definition of the callback passed to this.setState()?
this.setState(
{
openA:true
},
() => {
this.setState({
openB: false
})
}
)
This can be done, and this will result in 2 re-renders instead of 1. Usually there is no need to do this.
If setState are independent, it can be:
this.setState({ openA:true });
this.setState({ openB:false });
If updated states depend on each other, updater function should be used:
this.setState({ openA:true });
this.setState(state => ({ openB: !state.openA }));
Yes. It will run it after the first setState is complete.
Related
Hi I am using Formik in my component, where a method call used like below
addMetadata(values) {
console.log(values);
let newState = update(this.state, {
pro: { $set: values}
});
console.log(newState); // this point result print as expected
this.setState(newState);
console.log(this.state); // but here state not showing update result
}
where my state looks like
this.state = {
pro: {
key1: '',
key2: []
key3: {}
}
}
but states are not updating, can anyone know why ?
this.setState(newState) is asynchronous (or at least, it can be). Putting a log statement on the next line will not work, because the state hasn't been set yet.
In the rare cases where you need to know when the setState is done, you can provide a callback as the second argument to setState, which will be called once its complete:
this.setState(
newState,
() => {
console.log(this.state);
}
)
snapshot.forEach(function(childSnapshot) {
groupRef.child(childSnapshot.key).once("value", (snap) => {
this.setState.bind({
expenses: this.state.expenses.concat(snap.val()),
});
})
})
The error which I get is
undefined is not an object (evaluating '_this2.setState')
The state has been declared in the following way
constructor(props) {
super (props);
this.state = {
expenses: [],
};
}
It's because you're not calling or using bind correctly and you are referencing this which is different in the context you created by calling forEach with a traditional function callback. The first argument to bind is the context not arguments.
In this case just avoid it and use arrow functions so this is still your component reference.
snapshot.forEach((childSnapshot) => {
groupRef.child(childSnapshot.key).once("value", snap =>
this.setState(state => ({ expenses: [ ...state.expenses, snap.val()] }))
);
});
Note that you should use the callback style of setState as above when referencing existing state, since setState is asynchronous by the time your state is set it might have changed especially when setting in a loop in an async callback.
This is how my state looks like:
constructor(props, context) {
super(props, context);
this.state = {
show: false,
btnLabel: 'GO!',
car: {
owner: false,
manufacturer: false,
color: false
}
};
}
and this is how I modify state:
handleClickFetchPrice() {
this.setState({btnLabel: 'Fetching data...' });
console.log(this.state.fetchPriceBtn);
const url = 'some url';
axios.get(url)
.then(res => {
let car = [...this.state.car];
car.owner = res.data.owner;
car.manufacturer = res.data.manufacturer;
car.color = res.data.color;
this.setState({car});
})
}
The attribute car is updated, but fetchPriceBtn is not - the output of console.log(this.state.fetchPriceBtn); is still GO!.
What am I overlooking? Why the fetchPriceBtn is not updated?
React setState is an asynchronous process - you don't know exactly when it will be updated, you can only schedule the update.
To achieve your desired functionality, you can provide a callback into the setState method.
this.setState({ btnLabel: 'Fetching data...' }, () => console.log(this.state.fetchPriceBtn))
You can learn more following the documentation on the method.
#christopher is right, setState is an asynchronous process. But when second time call handleClickFetchPrice() function your btnLabel is value will be equal to Fetching data...
As answered in previous answers setState is asynchronous, so your console.log can't catch up the state change immediately. Again as suggested you can use callback function to track this change but if you use console.log just for debugging or want to see what changes in your state you can do this in your render function. And using a callback just for debug is not a nice way. Its purpose somehow different and if you check the official documentation, componentDidMount method is being suggested for such logic.
render() {
console.log( this.state.foo );
return (...)
}
If you do that you see two console.log output, one before state change and one after.
Also, your state operations might be enhanced. You car property is not an array, but you are converting it to an array and setting it? Is this what you intend:
axios.get(url)
.then(res => {
const { owner, manufacturer, color } = res.data;
this.setState( prevState => ( { car: { ...prevState.car, owner, manufacturer, color } } ) );
})
Here we are not mutating our state directly, instead we are using spread operator and setting the desired properties. For your example we are setting the whole property actually.
One last note, I think you want to do that something like that:
this.setState( { btnLabel: "fetching } );
axios.get(url)
.then(res => {
const { owner, manufacturer, color } = res.data;
this.setState( prevState => ( { car: { ...prevState.car, owner, manufacturer, color }, btnLabel: "go" } ) );
})
If your intention is somehow to do a status change/check this might no be a good logic as you have seen setState is not synchronous. Do this carefully.
I am new to Immutability Helper React library, and I am trying to update multiple state values at the same time, but only the last calling method state is getting updated.
this is what I tried:
state : {l0: null, l1: null}
updateL0 = (l0) => {
if(l0){
this.setState(
update(this.state, {
l0: { $set: l0 }
})
);
}
};
updateL1 = (l1) => {
if(l1){
this.setState(
update(this.state, {
l1: { $set: l1 }
})
);
}
};
Current Output: l1: null, l2: Expected Value
Expected Output: l1: Expected Value , l2: Expected Value
You can't setState multiple times in one event handler this.state may be updated asynchronousl after setState.
From the documentation
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState will behave like this:
//sate is {name:"Ben",age:22}
this.setState({...this.state,age:23});
console.log(this.state.age);//will log 22
So if you setState multiple times in one event handler you are likely to not get the result you were hoping:
//sate is {name:"Ben",age:22}
this.setState({...this.state,age:23});
console.log(this.state.age);//will log 22
this.setState({...this.state,name:"Harry"});//age will still be 22
The better solution to this is not to use the callback but to write your functions as pure functions (no side effects like setState), pass state to your functions and have them return a new state:
updateL0 = (state,l0) => {
if(l0){
return update(
state,
{
l0: { $set: l0 }
}
);
}
return state;
};
updateL1 = (state,l1) => {
if(l1){
return update(
state,
{
l1: { $set: l1 }
}
);
}
return state;
};
//when you call it you can do:
const newState = updateL0(this.state,L0);
this.setState(updateL1(newState,L1));//note that I'm passing newState here
//or you can just nest updateL1 and updateL0
this.setState(updateL1(updateL0(this.state,L0),L1));
use spread operator (...) in setState method to update multiple state fields as following way
my state is
this.state ={
fields: {
name:'',
email: '',
message: ''
},
errors: {},
disabled : false
}
i'm updating
this.setState(
{...this.state,
fields:{name:'', email: '', message: ''},
disabled: false
});
I have a function that sets state twice, however - the second setState has to occur after 500ms since first setState has occured (animation purposes).
Code looks like:
const showAnimation = () => {
this.setState({ hidden: false });
setTimeout(() => {
this.setState({ hidden: true });
}, 500);
};
However - if I do it this way, React somehow merges these two setState's into one and my animation doesn't work as expected.
But, if I use a hack:
const showAnimation = () => {
setTimeout(() => {
this.setState({ hidden: false });
}, 0); // ------------------------------> timeout 0
setTimeout(() => {
this.setState({ hidden: true });
}, 500);
};
It works as expected. But still, I don't really like it and Im afraid that it may be some kind of a hack. Is there any better solution for such case? Thanks :)
As setState are async in React you might not get updated state immediately but setState gives you prevState param in setState function to get last updated state so you won't merge state
The syntax goes like this in your case
this.setState((prevState) => { hidden: false }, () => {
setTimeout(() => {
this.setState({ hidden: !prevState.hidden });
}, 500);
});
just update your value to the updated state using prevState
If I understand your problem correct this should work fine
Please let me know if more clarification required
If you try something like that:
const showAnimation = () => {
this.setState({ hidden: false }, () => {
setTimeout(() => {
this.setState({ hidden: true });
}, 500);
}
}
I would personally use animations within JS if you are looking to time it without setTimeout. However this may be down to the face that 'setState' is async within react.
similar :
Why is setState in reactjs Async instead of Sync?
However react does expose a callback within setState - this works for me
this.setState(
{ hidden : false },
() => {
setTimeout(()=>{this.setState({hidden : true})}, 500)
}
);
For rendering performance, react batches calls to setState such that sequential calls will be executed together and will often be reflected in the same render cycle. Per the docs:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
In order to ensure that the first setState has been executed prior to your second call, you can pass setState a callback as the second argument. It's not perfect, but something like the following will ensure that your second call to setState will only happen once hidden: false.
const showAnimation = () => {
this.setState({ hidden: false }, () => setTimeout(() => {
this.setState({ hidden: true });
}, 500););
};