Input field unselects when I try and update - reactjs

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?

Related

this.setState isn't making changes in state

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

React set state not updating object data

I'm trying to update state using handleChangeProps method, but some how finally the fields.fileName is set as empty string instead of actual value. I'm using material-ui DropZone for file and for name TextField. The addNsmFile is called when onSubmit is called. The remaining fields: name, fileData are not empty and the actual value is set and I can get them in addNsmFile function. Can you help me to figure out why fields.fileName is set as empty finally?
const [fields, setFields] = useState({
name : '',
fileName: '',
fileData: ''
})
const handleChangeProps = (name, value) => {
if (name === 'name' ) {
if (validator.isEmpty(value.trim())) {
setNameError('Enter NSM Name')
} else if (value.trim().length > 512) {
setNameError('The maximum length for NSM name is 512. Please re-enter.')
} else {
setNameError('')
}
}
if (name === 'fileData') {
console.log('fileData', value)
if (validator.isEmpty(value.trim())) {
setFileuploadError('Drag and drop or browse nsm file')
} else {
setFileuploadError('')
}
}
setFields({ ...fields, [name]: value })
}
const addNsmFile = () =>{
let nsmForm = new FormData()
nsmForm.append('name', fields.name)
nsmForm.append('fileName', fields.fileName)
nsmForm.append('fileData', fields.fileData)
NSMDataService.addFile(nsmForm).then((response)=>{
if (response.data.substring(0, 1) === '0') {
const results = JSON.parse(response.data.substring(2))
addNotification('success', 'NSM file is being added successfully.')
//props.onRowSelectionChange(results.addFile)
props.setOpenDialog(false)
props.setRefresh(results.addFile[0].id)
} else if (response.data.substring(0, 1) === '1') {
addNotification('error', response.data.substring(2))
props.setOpenDialog(false)
}
}).catch((error)=>{
console.log(error)
})
}
<DropzoneArea
acceptedFiles={[".csv, text/csv, application/vnd.ms-excel, application/csv, text/x-csv, application/x-csv, text/comma-separated-values, text/x-comma-separated-values"]}
maxFileSize={1000000000} //1GB
dropzoneText='Drag and drop a NSM file here or click to add'
showFileNames= {true}
showPreviews={false}
useChipsForPreview={true}
showAlerts={true}
filesLimit={1}
classes={{root:classes.rootDropzoneArea, icon: classes.iconDropzoneArea, text: classes.textDropzoneArea}}
onChange={(files) => {
files.forEach((file) => {
console.log(file)
handleChangeProps('fileName', file.name)
let reader = new FileReader()
reader.onload = function(event) {
let contents = event.target.result
handleChangeProps('fileData', contents)
console.log("File contents: " + contents)
}
reader.onerror = function(event) {
console.error("File could not be read! Code " + event.target.error.code)
}
reader.readAsText(file);
})
}}
onDelete={(file)=> {
handleChangeProps('fileData', '')
}
}
/>
I think the problem might be with your setFields() update in handleChangeProps. Try to do like this:
setFields(prevState => ({
...prevState,
[name]: value,
}))

React - event.target.id no getting id from element

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}>

React js Change Field specifying the attribute name

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'}

What is the best way to update object array value in React

My React state:
//...
this.state = {
mylist: [
{
"id": 0,
"trueorfalse": false
},
{
"id": 1,
"trueorfalse": false
}
]
}
//...
I am trying to update the trueorfalse value based on the id
Here is what I did so far but didn't work:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
var TorF = true
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { TorF }) : el))
})
}
I really want to make it dynamic so the trueorfase will be opposite of what it is now:
var idnum = e.target.id.toString().split("_")[1] //getting the id via an element id (0 or 1 in this case)
if (type === 1) {
this.setState({
mylist: this.state.mylist.map(el => (el.id === idnum ? Object.assign({}, el, { /* if already true set to false or vice versa */ }) : el))
})
}
How can I update my code to have the dynamicity shown in the second example (if possible), otherwise the first example would do just fine
Another solution using map:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
mylist: [
{
id: 0,
trueorfalse: false
},
{
id: 1,
trueorfalse: true
}
]
};
}
toggleBoolean = () => {
const ID = Number(this.state.selectedID);
this.setState(prevState => ({
mylist: prevState.mylist.map(item => {
if (item.id === ID) {
return { ...item, trueorfalse: !item.trueorfalse };
} else {
return item;
}
})
}));
};
render() {
return (
<div className="App">
<p>{`State values: ${JSON.stringify(this.state.mylist)}`}</p>
<button onClick={this.toggleBoolean}>Change true/false values</button>
<label>Insert ID:</label>
<input
type="number"
onChange={event => this.setState({ selectedID: event.target.value })}
/>
</div>
);
}
}
I think the following code would accomplish your second question.
var idnum = e.target.id.toString().split("_")[1]
let newList = Array.from(this.state.mylist) //create new array so we don't modify state directly
if (type === 1) {
let objToUpdate = newList.find((el) => el.id === idnum) // grab first element with matching id
objToUpdate.trueorfalse = !objToUpdate.trueorfalse
this.setState( { mylist: newList } )
}

Resources