setState delay in react - javascript

I currently have a function in react native which does the following:
resetThenSet = (id, arrayId, title) => {
if(arrayId != 'selectProduct') {
// Setting state for each selected dropdown in selectedDropdowns
this.setState({
dataShowingToggle: true,
selectedDropdowns: {...this.state.selectedDropdowns, [arrayId]: title}
}, this.getProductCost(arrayId, id)
);
}
I run the above and I can confirm arrayId and title variables are valid and contain data. arrayId is also not 'selectProduct'. I added a console.log in there while debugging to ensure it runs, which it indeed does. The expected behavior I would expect is that the state is updated immediately.
However the selectedDropdowns isn't updated in state. When I add:
console.log(this.state) after the this.setState update there is no change. If I run the function twice it'll update on the second time it runs.
To test it even further I added static inputs like so:
this.setState({
dataShowingToggle: true,
selectedDropdowns: {...this.state.selectedDropdowns, testField: 'me here'}
}, this.getProductCost(arrayId, id)
);
console.log(this.state);
It only update state AFTER the first time it runs. Am I missing something?
UPDATE:
I updated the code to run the console.log on the call back to setstate:
if(arrayId != 'selectProduct') {
// Setting state for each selected dropdown in selectedDropdowns
console.log(arrayId + title);
console.log('i run');
this.setState({
dataShowingToggle: true,
selectedDropdowns: {...this.state.selectedDropdowns, [arrayId]: title}
}, console.log(this.state)
);
};
console.log is 'quantityProduct 25' for the arrayId + title
console.log(I run)
and then this state is NOT showing the quantityProduct 25

setState is asynchronous. This is why the function call has an optional callback.
I think you might have other issues.
Try putting that console log inside the callback function when you call setState to see when the state is updated with the values you gave it.

Related

How do I update useState immidiatly for function React

I have created a function to calculate the winner between the two selected pokemon. However, instead of using the newly selected option, it is using the previously selected option. It has been brought to my attention that this is because useState is not updating immediately so how would I go about fixing this?
Here is my winner function:
function selectedWinner(){
console.log(pokemonName+' '+pokeOneTotal);
console.log(pokemonName2+' '+pokeTwoTotal);
if(pokeOneTotal>pokeTwoTotal){
setPokemonWinner(pokemonName);
}else if(pokeOneTotal<pokeTwoTotal){
setPokemonWinner(pokemonName2);
}else{
setPokemonWinner("Draw");
}
}
I have set it so that it is called in the different select functions, which are on click functions, here is one as an example:
function optionOneSelected(){
console.log('selected');
axios.get('https://pokeapi.co/api/v2/pokemon/'+ pokemonOne.current.value)
.then((res)=>{
let data=res.data;
console.log(data);
let type = data.types[0].type.name;
let id = data.id;
let height= data.height;
let weight = data.weight;
let name = data.forms[0].name;
let hp = data.stats[0].base_stat;
//console.log(type)
setPokemonType(type);
setPokemonId(id);
setPokemonHeight(height);
setPokemonWeight(weight);
setPokemonName(name);
setPokemonHp(hp);
let sum=0;
sum= data.stats[0].base_stat+ data.stats[1].base_stat+ data.stats[2].base_stat+ data.stats[3].base_stat+data.stats[4].base_stat+data.stats[5].base_stat;
setPokeOneTotal(sum);
let pokemonOneDataList = [
data.stats[0].base_stat, data.stats[1].base_stat, data.stats[2].base_stat, data.stats[3].base_stat,data.stats[4].base_stat,data.stats[5].base_stat
];
let labels = [
'hp', 'Attack', 'Defense', 'Special Attack', 'Special Defense', 'Speed'
];
setPokemonOneData(pokemonOneDataList);
setDataLabels(labels);
selectedWinner();
})
}
You can call useEffect with pokeOneTotal and pokeTwoTotal as dependencies. Whenever pokeOneTotal or pokeTwoTotal updates, it will trigger useEffect
useEffect(() => {
if(pokeOneTotal>pokeTwoTotal){
setPokemonWinner(pokemonName);
}else if(pokeOneTotal<pokeTwoTotal){
setPokemonWinner(pokemonName2);
}else{
setPokemonWinner("Draw");
}
}, [pokeOneTotal, pokeTwoTotal])
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
// Whatever we want to do after the state has been updated.
}, [state])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "state" in the example is interchangeable with whatever state piece you're dealing with.
Check the documentation for more info.
Either:
Pass the new values to selectedWinner as arguments instead of reading from the state.
Move the call to selectedWinner into a separate useEffect hook that has those state variables as dependencies (so it gets called when, and only when, any of them change).

ReactJS: Passing state to child returns undefined

I am setting the state in a parent component. Then when that state is set (with componentDidMount) I need to pass that to a child.
Currently, since it is returning undefined in the child component. I presume this is because state isn't set at the moment that the non-existent state is being passed. Making it undefined.
Tried putting boolean conditions in for state so it needs to be true before passing. Booleans in the child, in the parent, anywhere i thought it might make a difference and would prevent the component for looking for the prop I'm passing before it exists.
yearArr is being set here with the result of a function being executed as the component is mounted
componentDidMount() {
const yearArrSorted = dataSet && this.playFunction();
this.setState({
locationData: dataSet,
yearArr: yearArrSorted
});
this._layerRendering();
this._animate();
}
here is the func:
playFunction = () => {
//i know this is unreadable, but i wanted to see how simple i could go.
return dataSet.map(i => {
return Object.keys(Object.values(i)[2])
.map(i => (i.startsWith(9) ? 19 + i : 20 + i))
.sort();
})[0];
};
Here's state:
this.state = {
locationData: "",
year: 95,
elevationScale: elevationScale.min,
yearArr: []
};
Hjere is where state is being passed, with my terribly hacky approach to try to get the state only being passed once its set:
<YearSelector
yearOnChange={this.yearOnChange}
year={this.state.year}
dataStateChange={this.dataStateChange}
dataSet={dataSet}
years={this.state.yeaArr.length > 0 && this.state.yeaArr}
/>
I expect to send state to the child component as a prop when it is set so that prop can be used to .map through the prop array and create a <select> DOM element with <option>
Can edit question as needed and code can be supplied.
Sorted the issues.
Tried this initially but it didn't work for whatever reason.
years={this.state.yearArr && this.state.yearArr}
Now it works, the method i tried at first, which is a status quo for react.

Reactjs: Checkbox state is update, then reverted when callback function terminates

I'm pretty new to react and I ran into a weird issue today. I have a function called handleCheckBoxClick()
handleCheckboxClick : function (e) {
this.setState({isChecked : e.target.checked},
function () {
if (this.state.isChecked) {
this.props.addToTransactionSubmissions(
this.props.submissionGuid,
this.props.paymentInfo
);
} else {
this.props.removeFromTransactionSubmissions(
this.props.submissionGuid
);
}
}
);
}
This particular function calls a function passed down through a parent called addTo/removeFromTransactionSubmission. The code for both is as follows:
addToTransactionSubmissions : function (guid, paymentInfo) {
if (Object.keys(this.state.transactionSubmissions).length === 0) {
this.setState({submissionType : paymentInfo.status_description.toLowerCase()},
function () {
console.log('it set the state though');
console.log(this.state.transactionSubmissions);
this.toggleButtons(this.state.submissionType);
}
);
}
var newTansactionSubmissions = update(this.state.transactionSubmissions,
{
$merge : {[guid] : paymentInfo}
});
this.setState({transactionSubmissions : newTansactionSubmissions},
function () {
console.log('state is now', this.state.transactionSubmissions);
}
);
},
removeFromTransactionSubmissions : function (guid) {
if (Object.keys(this.state.transactionSubmissions).length === 0) {
this.setState({submissionType : undefined},
function () {
this.toggleButtons(this.state.submissionType);
}
);
}
var newTransactionSubmission = update(this.state.transactionSubmissions,
{
[guid] : {$apply: function (x) {return undefined}}
});
this.setState({transactionSubmissions : newTransactionSubmission},
function () {
console.log('here in remove Transaction');
});
}
The problem I run into is that when addTo/removeFromTransactionSubmissions is called, the checkbox does not changes states, even though the state is changed before addTo/removeFromTransactionSubmissions is called. Through further debugging using Firebug, I discovered that 1) the functions are all being called properly, 2) if I do not set state in addTo/removeFromTransactionSubmissions everything runs without a hitch, and 3) the checkbox becomes unchecked after handleCheckboxClick completely finishes.
I suspect that for whatever reason, the state is being lost when Reactjs is trying to update the DOM. However, I do not know why this is the case and don't know how to further debug. And for clarification, the checkbox is in the child component whereas the transactionSubmissions state is in a parent, and on the click of a checkbox, transactionSubmissions is modified (Child Action modifies Parent state). If this is the wrong way to go about the problem, please tell me.
Basically what I want to do is every time I click/unclick a box, it removes the corresponding object to/from a map of ids to the object. Am I doing something incorrectly?
Thanks for the help!
I think you should use another aproach.
handleCheckboxClick : function (e) {
this.setState({isChecked : e.target.checked})
}
Add a method componentWillUpdate(https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate) or componentDidUpdate(https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate) and handle there the changes that must occur after state change
componentWillUpdate()
{
if (this.state.isChecked)
{
this.props.addToTransactionSubmissions(
this.props.submissionGuid,
this.props.paymentInfo);
} else {
this.props.removeFromTransactionSubmissions(this.props.submissionGuid);
}
}
Also you should not call setState sequencially, it can throw some errors by trying to mutate a component while it was updating.
Every time your code calls setState, react goes trought all the dom, check what has changed and render the changes again, saving processing. Ref:https://facebook.github.io/react/docs/component-api.html#setstate

sportsBasketballChange another place its not setting state properly

I tried to say is I am not able to set the value using setState in sportsBasketballChange function but I am able to set it in sportsSoccerChange function
i am new to react.js
i am trying to set the value using setState.
in sportsSoccerChange function its correctly setting setState.
but sportsBasketballChange another place its not setting state properly.
can you guys tell me how to fix it.
providing my code below.
part of code
sportsSoccerChange(value) {
this.props.onChange();
let processedValue = value;
// sportsMessages sportsAirFalling
processedValue = sportsAirFallBALL(processedValue, this.props.sportsAirFall);
// sportsSuceessOnTime-ation
let sportsSuceessOnTime-ationResult = sportsSuceessOnTime-ateBALL(processedValue, this.props.sportsDrive);
if (sportsSuceessOnTime-ationResult === true) {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' });
///here i get value as sportsSuceessOnTime-
}
//this.setState({ isBALLValid: sportsSuceessOnTime-ationResult });
// formatting
processedValue = formatBALL(processedValue, this.props.sportsLongJump);
// set value in local component state
this.setState({ sportsMessagesValue: processedValue });
},
sportsBasketballChange() {
if (this.state.sportsOutcome === 'female') {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' });
///here i don't get value as sportsSuceessOnTime-
}
},
whole code here
https://gist.github.com/js08/e20c02bf21242201c1525577d55dedbc
I'm assuming that you are checking the value of this.state at those commented lines, either using logging or debugging.
setState is asynchronous. This means that there is no guarantee that the changes have occurred by the time you reach the next line of code. However, setState allows you to provide a callback function to be run after the state has finished updating. That is where you should be checking the updated value.
sportsBasketballChange() {
if (this.state.sportsOutcome === 'female') {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' },
function(){
console.log(this.state.sportsOutcome); // == 'sportsSuceessOnTime-'
}
);
console.log(this.state.sportsOutcome); // untrustworthy
}
},

react componentdidupdate provokes infinite loop

I trigger an API call to elasticsearch with onChange so that I can prompt a list for autocomplete.
To ensure that my store was updated before rerender I added componentDidMount so that I am not one tick behind. But getting to the point needs code, so:
constructor(props) {
super(props)
this.state = {
inputValue: '',
options: []
};
this.props = {
inputValue: '',
options: []
};
this._onChange = this._onChange.bind(this);
}
componentDidMount() {
CodeStore.addChangeListener(this._onChange);
}
_onChange(event) {
if (this.state.inputValue && !event) {
var enteredChars = this.state.inputValue;
console.log('NO EVENT!');
} else if (event.target.value) {
console.log('EVENT!');
var enteredChars = event.target.value;
this.setState({inputValue: enteredChars});
}
CodeService.nextCode(enteredChars);
}
As you can see I added some log events just to get sure my condition is doing right. I read about that setState provokes a rerender so it is inside the condition but that didn't had stopped the loop . And the console log confirms the condition switch. But having the setState inside my condition brakes the functionality and I do not get a list.
Here is my log:
0
Home.jsx:48 EVENT!
Home.jsx:50 0
CodeService.js:27 request
CodeActions.js:10 dispatch
CodeStore.js:22 store
Home.jsx:43 1
Home.jsx:46 NO EVENT!
10OptionTemplate.jsx:15 render-list
CodeService.js:27 request
CodeActions.js:10 dispatch
CodeStore.js:22 store
Home.jsx:43 1
Home.jsx:46 NO EVENT!
10OptionTemplate.jsx:15 render-list
CodeService.js:27 request
CodeActions.js:10 dispatch
CodeStore.js:22 store
Home.jsx:43 1
Home.jsx:46 NO EVENT!
The infinity loop does not hurt the performance anyhow. I think because of componentWillUnmount but the massive amount of API calls have to be avoided. Hope this is enough information for any evidence.
Looks like the infinite loop is caused by the following pattern:
user types input
_onChange updates state + fires action to store
Store updates itself and send change event
Store change event fires same _onChange event
_onChange does not update state, but fires action to store nonetheless
Goto step 3. and repeat (indefinitely)
Steps to take to fix:
make different change handlers for user input and store update (as already suggested by #Janaka Stevens)
put the data from store in state
user input does not have to be in state (if your react code does not provide a value to an input field, it will start with empty value, and leave whatever is typed alone)
Then your code could look something like this:
constructor(props) {
super(props)
this.state = {
options: []
};
// removed the this.props bit: props should not be updated inside component
this._onChangeUser = this._onChangeUser.bind(this);
this._onChangeStore = this._onChangeStore.bind(this);
}
componentDidMount() {
CodeStore.addChangeListener(this._onChange); // this is OK
}
_onChangeUser(event) {
console.log('EVENT!');
var enteredChars = event.target.value;
CodeService.nextCode(enteredChars);
// No need to update state here - go to sleep until store changes
}
_onChangeStore() {
var newList = getListFromStore(); // your own function to get list from store
this.setState({ options: newList}); // update state here
}
Not sure what you meant by 'one tick behind', or what could have caused it, but cleaning up endless loop is a good place to start ;)
PS: Code above is sketch only, may have typo's.
It looks like you are using _onChange for both your input event and store listener. They need separate methods.

Categories

Resources