I'm trying to get the id from the element and then update it's state to the new id value.
The function where I handle the update:
updateID = (e) => {
console.log("IDFEEDING::" + this.state.id.feeding);
const { id, name } = e.target;
console.log("HereID::" + id + "," + name);
this.setState((prevState) => {
const updatedID = {
...prevState.id,
[name]: id
};
return {
id: updatedID
}
})
}
and here the element where I call the function to update the values. Update values works, but id no.. doesn't gives me erros, it's just doesn't update, I can't get the new id when I select a new radio button.
<RadioGroup id={this.state.id.feeding} aria-label="quiz" name="feeding" value={this.state.group.feeding}
onChange={(event) => {
var group = { ...this.state.group };
group.feeding = event.target.value;
this.setState({ group });
this.updateTotal(event)
var id = { ...this.state.id };
id.feeding = event.target.id;
this.setState({ id });
this.updateID(event)
}}>
Thanks in advance :)
setState is async, so it won't update the value immediately. You would need to make use of callback approach.
Also since you are executing many things within the onClick handler, I recommend you take it out into new function
updateID = ({ id, name }) => {
console.log("IDFEEDING::" + this.state.id.feeding);
console.log("HereID::" + id + "," + name);
this.setState((prevState) => {
const updatedID = {
...prevState.id,
[name]: id
};
return {
id: updatedID
}
})
}
onChange= (event) => {
var group = { ...this.state.group };
const {id, value, name } = event.target;
group.feeding = value;
var id = { ...this.state.id };
id.feeding = id;
// Use one setState instance to update both
this.setState({ id, group }, () => {
// using on callback
this.updateTotal(group);
this.updateID({id, name})
});
}
}
...
<RadioGroup id={this.state.id.feeding} aria-label="quiz" name="feeding" value={this.state.group.feeding}
onChange={this.onChange}>
Related
I am trying to modify a state when a users input fields on my dashboard is changed. This is how the handler is intended to work:
If the state is empty. Create a user with the standard values and change its values to the changed inputs
If the user exists in the state, change the changed field in the state to the new value.
If the user does not exist. Add the user to the state and change the changed field to the new value.
I am doing this by calling this function on a change of any inputs:
const handleInputChange = (event, person) => {
let new_form_val = {
objectId: person._id,
name: person.name,
role: person.role,
privilege: person.privilege,
group: person.group
};
console.log("handle change function called")
if (formValues.length == 0)
{
console.log("formValues is empty")
new_form_val[event.target.name] = event.target.value
console.log("adding", new_form_val)
setFormValues([...formValues, new_form_val])
}
// console.log(event.target.value)
console.log("Change target id", event.target.id)
console.log("current formvalue before change", formValues)
let form_val = formValues.find((item) => item.objectId == event.target.id)
if (form_val) {
console.log("person found in formValues", form_val)
let index = formValues.indexOf(form_val)
formValues[index][event.target.name] = event.target.value
console.log("Changed already existing formvalue", formValues)
setFormValues(formValues)
}
else {
new_form_val[event.target.name] = event.target.value
console.log("new person in form value", new_form_val)
setFormValues([...formValues, new_form_val])
}
}
Later on I am using that function as an onChange event handler
useEffect(() => {
// GARL: https: //bobbyhadz.com/blog/react-push-to-state-array
setPeople([])
console.log("get users effetct ran")
axios.get('/api/getusers').then((response) => {
response.data.forEach((item, index) => {
setPeople(oldStatusArray => {
return [...oldStatusArray, <Person
key={index}
id={index+1}
_id={item._id}
name={item.name}
role={item.role}
privilege_id={item.privilege}
group_id={item.group}
onChange={(event) => handleInputChange(event, item)}
/>]
})
});
})
}, []);
The problem I am facing though is whenever the onChange function is called. The whole formValues sate is reset and replaced with the new changed state. For exmpale: I change user A to a new name and role and the change is logged to the console. I also Change User B and then C to a new group. Finally the state only has the changes made from C.
Here is the full code:
import Link from 'next/link';
import axios from 'axios';
import React, { useState, useEffect } from "react";
import Person from '../components/person' // Not actually a import
const Dashboard = () => {
const [people, setPeople] = useState([]);
const [formValues, setFormValues] = useState([]);
const handleInputChange = (event, person) => {
let new_form_val = {
objectId: person._id,
name: person.name,
role: person.role,
privilege: person.privilege,
group: person.group
};
console.log("handle change function called")
if (formValues.length == 0)
{
console.log("formValues is empty")
new_form_val[event.target.name] = event.target.value
console.log("adding", new_form_val)
setFormValues([...formValues, new_form_val])
}
// console.log(event.target.value)
console.log("Change target id", event.target.id)
console.log("current formvalue before change", formValues)
let form_val = formValues.find((item) => item.objectId == event.target.id)
if (form_val) {
console.log("person found in formValues", form_val)
let index = formValues.indexOf(form_val)
formValues[index][event.target.name] = event.target.value
console.log("Changed already existing formvalue", formValues)
setFormValues(formValues)
}
else {
new_form_val[event.target.name] = event.target.value
console.log("new person in form value", new_form_val)
setFormValues([...formValues, new_form_val])
}
}
useEffect(() => {
setPeople([])
console.log("get users effetct ran")
axios.get('/api/getusers').then((response) => {
response.data.forEach((item, index) => {
setPeople(oldStatusArray => {
return [...oldStatusArray, <Person
key={index}
_id={item._id}
name={item.name}
role={item.role}
privilege_id={item.privilege}
group_id={item.group}
onChange={(event) => handleInputChange(event, item)}
/>]
})
});
})
}, []);
const submit = (values) => {
// Submits state to backend for handling
}
return (
<div id="main">
<h1>Administration</h1>
{(people.length == 0) ?
<h1>Laddar innehållet..</h1> : people }
</div>
);
}
export default Dashboard;
Here is the output after changing the input fields a couple of times:
>> handle change function called
>> formValues is empty
>> adding - Object { objectId: "634ea9b368bd856cebfdddc0", name: "RADICATED", role: "...", privilege: "634ff6d42c7b67c5708e901b", group: "634ff7322c7b67c5708e901d" }
>> change target id 634ea9b368bd856cebfdddc0
>> current formvalue before change - Array []
>> new person in form value - Object { objectId: "634ea9b368bd856cebfdddc0", name: "RADICATED", role: "....", privilege: "634ff6d42c7b67c5708e901b", group: "634ff7322c7b67c5708e901d" }
>> CURRENT formvalues - Array [ {…} ] (len: 1)
I have also tried to adding formValues as a dependency to useEffect however, this results in a rerender of the users if I change any of the inputs as the setPeople is called in the useEffect.
How can I achieve a handleInputChange function that works as intended without updating the renderer or reseting the state?
I noticed the step 1 and 3 are actually the same so I put those together. The itemExists check if the person is already in the state. If the state is empty itemExists is false and if the person does not exists itemExists is also false.
When false we just update the field and return the previous and the new new_form_val.
When true we loop over all the current values until we find the one we want to edit, and then update the field we want to update.
const handleInputChange = (event, person) => {
const new_form_val = {
objectId: person._id,
name: person.name,
role: person.role,
privilege: person.privilege,
group: person.group,
};
// check if the item already exists
const itemExists =
formValues.find((item) => item.objectId == event.target.id) !== undefined;
if (itemExists) {
setFormValues((prevFormValues) => {
// map current values
const newValues = prevFormValues.map((item) => {
// if its not the item we're editing just return the item
if (item.objectId !== event.target.id) return item;
// if it is, update the item
const updatedItem = {
...item,
[event.target.name]: event.target.value,
};
return updatedItem;
});
return newValues;
});
} else {
// update the field with the new value
new_form_val[event.target.name] = event.target.value;
// add to the values
setFormValues((prevFormValues) => [...prevFormValues, new_form_val]);
}
};
I also updated the way the people were set. Now we first loop over all the data received from the api and create an array of Person components and set that array to the state, instead of setting the state for every result in the api data.
useEffect(() => {
// no need to set the people to an empty array since the default state is already an empty array
// setPeople([]);
console.log("get users effetct ran");
axios.get("/api/getusers").then((response) => {
const peopleFromApi = response.data.map((item, index) => (
<Person
key={index}
_id={item._id}
name={item.name}
role={item.role}
privilege_id={item.privilege}
group_id={item.group}
onChange={(event) => handleInputChange(event, item)}
/>
));
setPeople(peopleFromApi);
});
}, []);
I hope this helps you continue your project!
I am using functions that change a value in a nested object in the state :
an I am calling those functions in a button , they are executed when I click on that button , but one of those functions doesn't make changes to the state
This is the state :
state = {
data: {
attributesLength: this.props.product.attributes.length,
modalMessage: "",
isOpen: false,
},
};
and these are the functions :
addToCart = (id) => {
let data = { ...this.state.data };
if (Object.keys(this.state).length === 1) {
data.modalMessage = "Please, select product attributes";
this.setState({ data});
return;
}
if (
Object.keys(this.state).length - 1 ===
this.state.data.attributesLength
) {
const attributes = Object.entries(this.state).filter(
([key, value]) => key !== "data"
);
if (this.props.cartProducts.length === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
const product = this.props.cartProducts.filter((item) => item.id === id);
if (product.length === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
if (product.length !== 0) {
this.props.changeQuantity({ id: id, case: "increase" });
data.modalMessage = "Quantity increased !";
this.setState({ data });
return;
}
if (this.state.data.attributesLength === 0) {
this.props.addItem({
id: id,
quantity: 1,
attributes: Object.fromEntries(attributes),
});
data.modalMessage = "Added to cart !";
this.setState({ data });
return;
}
} else {
data.modalMessage = 'please, select "ALL" product attributes!';
this.setState({ data });
}
};
changeModalBoolean = () => {
let data = { ...this.state.data };
data.isOpen = !data.isOpen;
this.setState({ data });
};
and this is where I am calling functions :
<button
className={product.inStock ? null : "disabled"}
disabled={product.inStock ? false : true}
onClick={() => {
this.addToCart(product.id);
this.changeModalBoolean();
}}
>
{product.inStock ? "add to cart" : "out of stock"}
</button>
NOTE
changeModalBoolean function works and change state isOpen value,
this.addToCart(product.id);
this.changeModalBoolean();
This code run synchronously one after the other. In every function, you create a copy of previous state let data = { ...this.state.data };
so the this.changeModalBoolean(); just replace state which you set in this.addToCart(product.id); to fix this problem, use this.setState((state) => /*modify state*/)
changeModalBoolean = () => {
this.setState((state) => {
let data = { ...state.data };
data.isOpen = !data.isOpen;
return { data };
})
};
or modify the same object in both functions
I have attributes in the state, I would like to ensure that by specifying the function the attribute name changes the value contained in the state.
It seems to work, the problem that if I have an object of this type in the state:
companyInfo: {
name: "",
vatNumber: "",
legalRepresentative: ""
}
It does not work properly, as the code is now set in the state in this case a new attribute is created.
So I'd like to do something like this:
handleChangeField("companyInfo.name")
It is changed to the state atrribute name of the obj companyInfo that is in the state.
Can you give me some advice?
Link: codesandbox
Code:
import ReactDOM from "react-dom";
import React, { Component } from "react";
import ReactJson from "react-json-view";
class Todo extends Component {
constructor(props) {
super(props);
this.state = {
email: "email0",
role: "role0",
companyInfo: {
name: "",
vatNumber: "",
legalRepresentative: ""
}
};
}
returnStateElement = (...elements) => {
const copy = Object.assign({}, this.state);
return elements.reduce((obj, key) => ({ ...obj, [key]: copy[key] }), {});
};
handleChangeField = field => evt => {
let state = {};
state[field] = evt.target.value;
this.setState(state);
};
handleSubmit = () => {
let el = this.returnStateElement(
"name",
"email",
"vatNumber",
"legalRepresentative",
"role"
);
let { name, email, legalRepresentative, vatNumber, role } = el;
let dataSender = {};
dataSender.email = email;
dataSender.role = role;
dataSender.companyInfo = {};
dataSender.companyInfo.name = name;
dataSender.companyInfo.legalRepresentative = legalRepresentative;
dataSender.companyInfo.vatNumber = vatNumber;
console.log(this.state);
//console.log(dataSender)
};
render() {
return (
<div>
<input onChange={this.handleChangeField("email")} />
<br />
<br />
<input onChange={this.handleChangeField("companyInfo.name")} />
<br />
<br />
<button onClick={() => this.handleSubmit()}>send</button>
<br />
<br />
<ReactJson src={this.state} theme="solarized" />
</div>
);
}
}
ReactDOM.render(<Todo />, document.getElementById("root"));
Edit: I came up with a much better answer where one mutates the specific key of the oldState using a reduce. Less code, much more elegant and should work at any object depth.
Working example here
setNestedField(object, fields, newValue) {
fields.reduce((acc, field, index) => {
if (index === fields.length - 1) {
acc[field] = newValue;
}
return acc[field];
}, object);
return object;
}
handleChangeField = field => evt => {
const fields = field.split(".");
let oldState = this.state;
const newState = this.setNestedField(
{ ...oldState },
fields,
evt.target.value
);
this.setState(newState);
};
OLD ANSWER
handleChangeFields looks like this:
handleChangeField = field => evt => {
//first you split by '.' to get all the keys
const fields = field.split(".").reverse();
// you'll need the previous state
let oldState = this.state;
let newState = fields.reduce((acc, value, index) => {
if (index === 0) {
// you add the event value to the first key
acc[value] = evt.target.value;
return acc;
}
//copy acc to use it later
const tmp = { ...acc };
//delete previous key added to acc
delete acc[fields[index - 1]];
acc[value] = { ...oldState[value], ...tmp };
return acc;
}, {});
this.setState(newState);
};
What's going on step by step in the reduce function, if you do handleChangeField('company.name') with evt.target.value = "Big Corp":
1) you get the array ['name','company']
2) you go in the reduce function
when index = 0, acc = {}, key='name' => {name: 'Big Corp'}
when index=1, acc= {name: 'Big Corp'},key='company' => acc = { company: {name: 'Big Corp'}, name: 'BigCorp} so before returning we delete the previous key (name here) to return => { company: {name: 'Big Corp'}
I need to dynamically update the total when an item is added
selectedProducts is the array being updated onClick with the selected item, the item includes the price (in the object: 'price'), i need to get the price of the total items, and update totalPrice accordingly.
* I am trying to keep everything in one function if possible *
class App extends Component {
state = {
products: products,
selectedProducts: [],
totalPrice: 0,
};
handleQuantityChange = id => {
const carsSelected = this.state.selectedProducts;
const price = products.map(id => id.price).find((id)=> {
return id
} )
const priceAddition = price
const oldPrice = this.state.totalPrice;
const newPrice = oldPrice + priceAddition;
this.setState({
selectedProducts: [...carsSelected, id],
totalPrice:newPrice,
});
};
You can simplify your method with a functional form of setState:
handleQuantityChange = id => {
const product = products.find(product => product.id === id)
this.setState(state => ({
selectedProducts: [...state.selectedProducts, id],
totalPrice: state.totalPrice + product.price,
}));
};
Because find method does return matched record as array object.
so you have replace above your find method line with these lines
const product = products.find(product => product.id === id)
const {price=0}=product[0] && product[0] || {};
ive solved the issue, all i had to do was:
handleQuantityChange = id => {
const carsSelected = this.state.selectedProducts
const oldTotal = this.state.totalPrice
const newPrice = id.price + oldTotal
this.setState({
selectedProducts: [...carsSelected, id],
totalPrice: newPrice
});
};
thanks for everyone help
I'm trying to update the input value on a form. The input value I need to update sits within an array of objects within another array of objects. I'm trying to update the address within emails (see below).
const userProfiles = [{
firstName: 'John',
emails: [{ address: 'john#gmail.com' }]
}]
Each keystroke updates the field and sate of the userProfiles, however, the input field disengages. So I have to keep reselecting the input field. What am I missing here?
handleInputChange = (userProfileId, index) => (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
const userProfiles = this.state.userProfiles.map((userProfile) => {
if (userProfile._id === userProfileId) {
if (name === 'email') {
const emails = userProfile.emails.map((email, idx) => {
if (idx === index) {
return {
...email,
address: value,
};
}
return {
...email,
};
});
return {
...userProfile,
emails,
};
}
return {
...userProfile,
[name]: value,
};
}
return {
...userProfile,
};
});
this.setState({
userProfiles,
});
}
handleInputChange = (userProfileId, index) => (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
let { userProfiles } = this.state;
userProfiles.map((eachProfile) => {
let { emails } = userProfiles.emails;
if (userProfile._id === userProfileId) {
if(name === 'email') {
emails.map((emails, idx) => {
if (idx === index) {
emails = value;
}
})
}
}
});
this.setState({
...this.state,
userProfiles
})
}
Can you try this one?