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)}));
}
};
Related
I want to update value of one object only but updating value of one Object, Updates the value for all objects.
let default = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(default)
}
updateName (event) {
let index = event.target.id,
values = this.state.values;
values[index].name = event.target.value;
this.setState ({
values: values
});
}
There are four significant problems in that code.
You're using the same object for all entries in your array. If you want to have different objects, you have to create multiple copies of the default.
You're calling setState incorrectly. Any time you're setting state based on existing state (and you're setting values based, indirectly, on this.state.values), you must use the function callback version of setState. More: State Updates May Be Asynchronous
You can't directly modify the object held in this.state.values; instead, you must make a copy of the object and modify that. More: Do Not Modify State Directly
default is a keyword, you can't use it as an identifier. Let's use defaultValue instead.
Here's one way you can address all four (see comments):
// #4 - `default` is a keyword
let defaultValue = {
name: '',
age: ''
};
this.state = {
// #1 - copy default, don't use it directly
values: [
Object.assign({}, defaultValue),
Object.assign({}, defaultValue),
] // <=== Side note - no ; here!
};
// ....
updateName(event) {
// Grab the name for later use
const name = event.target.value;
// Grab the index -- I __don't__ recommend using indexed updates like this;
// instead, use an object property you can search for in the array in case
// the order changes (but I haven't done that in this code).
const index = event.target.id;
// #2 - state updates working from current state MUST use
// the function callback version of setState
this.setState(prevState => {
// #3 - don't modify state directly - copy the array...
const values = prevState.values.slice();
// ...and the object, doing the update; again, I wouldn't use an index from
// the `id` property here, I'd find it in the `values` array wherever it
// is _now_ instead (it may have moved).
values[index] = {...values[index], name};
return {values};
});
}
Note that this line in the above:
values[index] = {...values[index], name};
...uses property spread syntax added in ES2018 (and shorthand property syntax, just name instead of name: name).
I would use the Array.prototype.map function with combination of the object spread syntax (stage 4):
Note that i changed the name of the default object to obj.
default is a reserved key word in javascript
let obj = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(obj)
}
updateName(event){
const {id, value} = event.target;
this.setState(prev => {
const {values} = prev;
const nextState = values.map((o,idx) => {
if(idx !== id)
return o; // not our object, return as is
return{
...o,
name: value;
}
});
return{
values: nextState
}
});
}
There is an easy and safe way to achieve that through the following:
this.setState({
values: [ newObject, ...this.state.values],
});
this will create an instance of the state and change the value of an existing object with new object.
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();
I'm trying to get data from firebase/firestore using javascript so i made a function where i get my products collection and passing this data to reactjs state.products by setState() method
My goal is to pass these products to my react state but also keeping the original data and not changing it by manipulating it. I understand that in JavaScript whenever we are assigning the objects to a variable we are actually passing them as a reference and not copying them, so that' why i used the 3 dots (spread syntax) to copy firestore data into tempProducts[] same way to copy it in virgins[]
getData = () => {
let tempProducts = [];
let virgins = [];
db.collection("products")
.get()
.then(querySnapshot => {
querySnapshot.forEach(item => {
const singleItem = { ...item.data() };
virgins = [...virgins, singleItem];
tempProducts = [...tempProducts, singleItem];
});
tempProducts[0].inCart = true;
this.setState(
() => {
return { products: tempProducts };
},
() => {
console.log(
"this.state.products[0].inCart: " + this.state.products[0].inCart
);
console.log("tempProducts[0].inCart: " + tempProducts[0].inCart);
console.log("virgins[0].inCart: " + virgins[0].inCart);
}
);
});
};
then calling this method in componentDidMount
componentDidMount() {
this.getData();
}
So I changed the first product inCart value in tempProducts to true when I console log tempProducts value and state.products value it gives me true all fine but I'm expecting to get false when i console log virgins value but i did not. I should also mention that all inCart values are false in firestore data.
I solved the issue by passing the original firestore data to virgins[] instead of the singleItem as it is an object referenced by tempProducts[] like so virgins = [...virgins, item.data()]; also it works if i copied the singleItem object instead of referencing it like so virgins = [...virgins, { ...singleItem }]; keep in mind that i have no idea if this solutions are in fact efficient (not "memory waste") or not.
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] }));
I've got a function that I run in the forEach loop in the child component, the function has to filter the data and update the state with the result.
The problem is that when I iterate the function with a loop it running all at the same time and within this, the state will not update properly with data.
The question is how to implement it better, probably there is a way with Promise or async /await or maybe something simpler that will make a work. As needed to put in the queue and wait until the state will be updated.
Simplified code is here
component child
this.props.data.forEach((item, i) => {
this.props.update(item);
});
component parent
function update(data) {
let filtered = this.state.data.filter(item => item.uid !== data.uid);
this.setState({data: filtered});
}
If i understand well you need something like this:
update = () => {
let filtered = this.state.data.filter(x => this.props.data.every(y => x.uid !== y.uid))
this.setState({
data: filtered
})
}
Why not iterating through your array in your parent component?
Child:
this.props.update(this.props.data); // pass the entire array
Parent:
function update(data) {
let filtered = data.map(d=> {
return this.state.data.filter(item => item.uid !== d.uid);
}
this.setState({data: filtered});
}