useState won't update state - javascript

I'm trying to update a state using useState hook, however the state won't update.
const handleSelect = address => {
geocodeByAddress(address)
.then(address => {
const receivedAddress = address[0];
const newAddress = {
street: receivedAddress.address_components[1].long_name,
number: receivedAddress.address_components[0].long_name,
city: receivedAddress.address_components[3].long_name,
country: receivedAddress.address_components[5].long_name,
zip: receivedAddress.address_components[6].long_name,
selected: true
};
handleAddressSelection(newAddress);
})
.catch(error => console.log(error));
};
When handleSelect is called, it creates the object newAddress, and then calls handleAddressSelection passing newAddress.
function handleAddressSelection(newObj) {
console.log(newObj);
Object.keys(newObj).forEach(function(key) {
setValues({ ...values, [key]: newObj[key] });
});
}
In console.log(newObj) the object is filled fine, with all the data I need. Then I call setValues for each object in newObj, however no matter what, the values object won't receive the new data. The only one that is updated is selected: true, all others won't update.
What should I do to fix it?

You're calling setValues multiple times in a loop, and every time you do so, you spread the original values, and thus overwrite anything that was done on the previous setValues. Only the very last setValues ends up working, which happens to be the one for selected: true
If you need to base your update on the previous value of the state, you should use the function version of setValues, as in:
Object.keys(newObj).forEach(function(key) {
setValues(oldValues => ({ ...oldValues, [key]: newObj[key] }));
});
But even better would be to only call setValues once. If you're calling it multiple times, then you're going to generate multiple renders. I'd do this:
setValues(oldValues => ({...oldValues, ...newObj}));

Values is not even defined anywhere in your examples. My guess is, it's some cached copy and you should be using callback variant of the state setter instead:
setValues(previousValues => ({ ...previousValues, [key]: newObj[key] }));

Related

how i can update the value of object of array by use state

I recently started learning react.js for making my final year project using MERN stack
during this i am facing this issue, i.e. i have an array
let projectTech = ['js','node','react'];
and i want to concat it with technologies array present in following useState, please tell me how can i do it.
const [projectObj, setProjectObj] = useState({
title: '',
type: '',
link: '',
technologies:[],
role:'',
description: ''
});
i already tried following combinations but it's not working, after console.log(projectObj.technologies) i got empty array.
setProjectObj({...projectObj,technologies: projectTech});
setProjectObj({...projectObj,technologies:[...projectTech]});
setProjectObj({...projectObj,technologies:[...projectObj.technologies,projectTech]});
please help me, how can i fix this?
You can try using the prevState of the setState function.
setProjectObj((prevState) => ({...prevState, technologies: projectTech }))
check out this: https://codesandbox.io/s/nifty-browser-wmoseu?file=/src/App.js
Nothing is wrong with that code. You only have to change how you console.log your result.
Since setting state is async, try to avoid logging state after setting it.
Add this in your component
const handleSetState = () => {
setProjectObj({...projectObj,technologies: projectTech});
console.log(projectObj) //WRONG, will console.log previous data
}
const handleSetState2 = () => {
const newObj = {...projectObj,technologies: projectTech}
setProjectObj(newObj);
console.log(newObj) //OK, but not actual state.
}
//BEST
useEffect(() => {
console.log(projectObj) //Console.log state
}, [projectObj]) //But only if state changes

React setState hook updating one previous value

I have react js and asp.net core application, onform submit i am trying to fill some master data and detail data(with in aray).
The problem is that, setState updates one previous value and my backend asp.net web api method gets hit when i press submit button twice. anyone can please tell me what is the proper way to update state so that i could submit correct data on first hit. I am calling another function addDetailItem from handleSubmit function to fill array using setState hook.
<form autoComplete="off" noValidate onSubmit = {handleSubmit} >
const handleSubmit = (e) => {
e.preventDefault();
if(validateForm()){
addDetailItem();
props.createPaymentVoucher(values);
}
console.log(values);
}
const addDetailItem = () => {
let x = {
voucherMasterId:values.voucherMasterId,
voucherDetailId:0,
accountCode: values.creditAccountCode,
debit: 0,
credit: values.creditAmount,
remarks: values.fromRemarks,
status: ""
}
setValues({
...values,
voucherDetails: [...values.voucherDetails, x]
});
console.log('voucherDetails: ', values.voucherDetails);
}
here setValues setting the values but on first click it shows empty array and on second click it shows values which had to filled first time.
Solution
Just capture values changes using useEffect
I passed the values as dependency in useEffect.
So, You will get the latest updated value of values
Now everytime anything changes in values, it will call props.createPaymentVoucher
useEffect(() => {
props.createPaymentVoucher(values);
}, [values])
const handleSubmit = (e) => {
e.preventDefault();
if(validateForm()){
addDetailItem();
}
}
const addDetailItem = () => {
let x = {
voucherMasterId:values.voucherMasterId,
voucherDetailId:0,
accountCode: values.creditAccountCode,
debit: 0,
credit: values.creditAmount,
remarks: values.fromRemarks,
status: ""
}
setValues({
...values,
voucherDetails: [...values.voucherDetails, x]
});
}
setState is async function so when you try to use state immediately after updating it, it will always return previous value.
So instead of using state value immediately, you can use useEffect or either follow below solution for your specific use case.
Try:-
const newValues = { ...values, voucherDetails: [...values.voucherDetails, x]};
setValues({ ...newValues });
return newValues;
Now you can access your values in your handleSubmit function like below
const updatedValues = addDetailItem();

How to remove unchecked checkbox from React state array?

With a checkbox onChange event, how do I remove value from state array when unchecked in react?
State array:
this.state = { value: [] }
onChange function:
handleChange = event => {
if (event.target.checked) {
this.setState({
value: [...this.state.value, event.target.value]
});
} else {
this.setState({
value: [this.state.value.filter(element => element !== event.target.value)]
});
}
};
Not sure exactly what the .filter() should be doing
You're very close, except:
You need to remove the [] around your call to filter. filter returns an array. If you wrap that in [], you're putting the array inside another array, which you don't want (in this case).
and
Since you're updating state based on existing state, it's important to use the callback version of setState, not the version that directly accepts an object. State updates can be batched together, so you need to be sure you're dealing with the most recent version of the array.
So:
handleChange = ({target: {checked, value: checkValue}}) => {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ^− destructuring to take the properties from the event,
// since the event object will get reused and we're doing
// something asynchronous below
if (checked) {
this.setState(({value}) => ({value: [...value, checkValue]}));
} else {
this.setState(({value}) => ({value: value.filter(e => e !== checkValue)}));
// ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^−−− No [] around this
}
};
There are some situations where you'd get away with using this.state.value instead of using the callback (for instance, if you only update value in response to certain events), but you have to be sure you know which ones they are; it's simpler just to use the callback.
FWIW, since it has multiple values in it, if it were me I'd call the state property values (plural) rather than value, which would also mean we didn't have to rename the value from the event target in the destructuring above:
handleChange = ({target: {checked, value}}) => {
if (checked) {
this.setState(({values}) => ({values: [...values, value]}));
} else {
this.setState(({values}) => ({values: values.filter(e => e !== value)}));
}
};

add row without push in es6 for react state

I'm not sure I'm doing the right thing, I mutate variable outside of setState, it's fine right? or there's more elegant way to do it?
state = {
persons: [{
name: 'jay',
age: 10
}]
}
addRow = () => {
const temp = this.state
temp.persons.push({
name: '',
age: ''
})
this.setState({
...temp
})
}
App demo https://codesandbox.io/s/ppqw4wjqzq
In javascript, object assignment works by referece and hence Even if you mutate the variable outside of setState, it will still refer to the same reference of state as long as you do not clone your object. However if you clone it, a new instance will be created and the original one will not be affected
addRow = () => {
const persons = [...this.state.persons] // Clone it one level deep using spread
persons.push({
name: '',
age: ''
})
this.setState({
persons
})
}
The above can be done using simply spread syntax and functional setState like
addRow = () => {
this.setState(prevState => ({
persons: [...prevState.persons, { name: '', age: ''}]
}))
}
Although in your example there seems no difference between the two actions, there is major flaw in the initial implementation that you provided. In order to see the difference between cloning and pushing and just assigning the reference and pushing, you can see the codesandbox demo.
Basically when you create a new component to which if you pass the state persons as props, and you mutate at its original reference, in the componentWillReceiveProps method, you see that the currentProps and the nextProps are both the same and hence if you have any check in the child component to take action if the persons prop changed, that would fail. Hence its extremely important to not mutate the value at its own reference
Without push and spread syntax, you can still avoid the mutation issue by using concat which create a new copy of the original array
addRow = () => {
this.setState(prevState => ({
persons: prevState.persons.concat([{ name: '', age: ''}])
}))
}
In my opinion, more elegant way would be to use functional setState:
const newPerson = { name: '', age: -1 };
this.setState(prevState => ({ persons: [...prevState.persons, newPerson] })

Correct way to push into state array

I seem to be having issues pushing data into a state array.
I am trying to achieve it this way:
this.setState({ myArray: this.state.myArray.push('new value') })
But I believe this is incorrect way and causes issues with mutability?
Using es6 it can be done like this:
this.setState({ myArray: [...this.state.myArray, 'new value'] }) //simple value
this.setState({ myArray: [...this.state.myArray, ...[1,2,3] ] }) //another array
Spread syntax
Array push returns length
this.state.myArray.push('new value') returns the length of the extended array, instead of the array itself.Array.prototype.push().
I guess you expect the returned value to be the array.
Immutability
It seems it's rather the behaviour of React:
NEVER mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.React.Component.
I guess, you would do it like this (not familiar with React):
var joined = this.state.myArray.concat('new value');
this.setState({ myArray: joined })
Functional Components & React Hooks
const [array,setArray] = useState([]);
Push value at the end:
setArray(oldArray => [...oldArray,newValue] );
Push value at the start:
setArray(oldArray => [newValue,...oldArray] );
Never recommended to mutate the state directly.
The recommended approach in later React versions is to use an updater function when modifying states to prevent race conditions:
Push string to end of the array
this.setState(prevState => ({
myArray: [...prevState.myArray, "new value"]
}))
Push string to beginning of the array
this.setState(prevState => ({
myArray: ["new value", ...prevState.myArray]
}))
Push object to end of the array
this.setState(prevState => ({
myArray: [...prevState.myArray, {"name": "object"}]
}))
Push object to beginning of the array
this.setState(prevState => ({
myArray: [ {"name": "object"}, ...prevState.myArray]
}))
You should not be operating the state at all. At least, not directly. If you want to update your array, you'll want to do something like this.
var newStateArray = this.state.myArray.slice();
newStateArray.push('new value');
this.setState(myArray: newStateArray);
Working on the state object directly is not desirable. You can also take a look at React's immutability helpers.
https://facebook.github.io/react/docs/update.html
Here you can not push the object to a state array like this. You can push like your way in normal array.
Here you have to set the state,
this.setState({
myArray: [...this.state.myArray, 'new value']
})
You can use .concat method to create copy of your array with new data:
this.setState({ myArray: this.state.myArray.concat('new value') })
But beware of special behaviour of .concat method when passing arrays - [1, 2].concat(['foo', 3], 'bar') will result in [1, 2, 'foo', 3, 'bar'].
Using react hooks, you can do following way
const [countryList, setCountries] = useState([]);
setCountries((countryList) => [
...countryList,
"India",
]);
This Code work for me :
fetch('http://localhost:8080')
.then(response => response.json())
.then(json => {
this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
})
React-Native
if u also want ur UI (ie. ur flatList) to be up to date, use PrevState:
in the example below if user clicks on the button , it is going to add a new object to the list( both in the model and UI)
data: ['shopping','reading'] // declared in constructor
onPress={() => {this.setState((prevState, props) => {
return {data: [new obj].concat(prevState.data) };
})}}.
In the following way we can check and update the objects
this.setState(prevState => ({
Chart: this.state.Chart.length !== 0 ? [...prevState.Chart,data[data.length - 1]] : data
}));
setState([...prevState, {
label: newState.name,
value: newState.id
}]);
Was working with the dropdowns and wanted to implement this scenario there, i found this simple solution for dropdown with multiple values.
you are breaking React principles, you should clone the old state then merge it with the new data, you shouldn't manipulate your state directly,
your code should go like this
fetch('http://localhost:8080').then(response => response.json()).then(json ={this.setState({mystate[...this.state.mystate, json]}) })
If you use:
const[myArr, setMyArr] = useState([]);
for add:
setMyArr([...myArr, value]);
and for remove:
let index = myArr.indexOf(value);
if(index !== -1)
setPatch([...myArr.slice(0, index), ...myArr.slice(index, myArr.length-1)]);
I guess this is a little bit late for an answer but for those new to react
You can use this tiny package called immer
see this example: https://immerjs.github.io/immer/produce

Categories

Resources