React js Change Field specifying the attribute name - reactjs

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

Related

I don't understand why I am getting the value of dob as an object when in use react-datepicker

when I console log the value of dob, its an object instead of a string
initial state
const initialValues = {
registrationForm: {
dob: {
elementType: 'date',
elementConfig: {
name: 'dob',
required: true,
placeholderText: 'Date of birth'
},
value: null,
},
},
}
state*
const [values, setValues] = useState(initialValues);
updateObject
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
handle change handler
const handleChange = (event, inputIdentifier, config) => {
const updatedRegistrationForm = updateObject(values.registrationForm, {
[inputIdentifier]: updatedFormElement
});
setValues({registrationForm: updatedRegistrationForm);
};
handle submit*
const handleSubmit = (event) => {
event.preventDefault()
const formData = {};
for (let formElementIdentifier in values.registrationForm) {
formData[formElementIdentifier] = values.registrationForm[formElementIdentifier].value;
}
console.log(formData)
};
for (let key in values.registrationForm) {
formElementsArray.push({
id: key,
config: values.registrationForm[key]
});
}
form
<form onSubmit={handleSubmit}>
{
formElementsArray.slice(sliceStart, sliceNumber).map(({config, id}) => {
return <Input
key={id}
elementType={config.elementType}
elementConfig={config.elementConfig}
value={config.value}
changed={(event) => handleChange(event, id)}/>
})
}
</form>
input
const Input = ({
elementType,
elementConfig,
value,
changed,
}) => {
let inputElement = null;
switch (elementType) {
case ('date'):
inputElement =
<DatePicker
id={id
className='mb-3 form-control'
{...elementConfig}
selected={value}
onChange={changed}/>
break;
}
return (
<>
{inputElement}
</>
);
};
export default Input;
I was expecting something similar to this when I console log(Stringvale)
dob: "2023-01-11T21:00:00.000Z"
but I got this(an object)
Any help will be highly appreciated.
<DatePicker
id={id
className='mb-3 form-control'
{...elementConfig}
selected={value}
onChange={changed}/>
onChange in DatePicker returns a date object. You can use any date formatters to convert the date object to required format.
Update changed method to accept the date object, do the conversions like below.
const handleChange = (event, inputIdentifier, config) => {
const updatedRegistrationForm = updateObject(values.registrationForm,
{
[inputIdentifier]: updatedFormElement
});
if(inputIdentifier=="dob") {
updatedFormElement.value=formatDate(date); //create some function to do the date formatting
} else {
updatedFormElement.value = event.target.value // for example
// logic for non date inputs
}
setValues({registrationForm: updatedRegistrationForm);
};

Using setState inside a arrow function resets state

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!

How to set value for the checkbox in ReactJS

I try to show my value using checkbox. Value always comes for the console log. But it didn't set for the checkbox. Here is the code and image for my problem:
var NotePage = createClass({
addTags(e) {
console.log("id****************", e.target.id);
let id = e.target.id;
let selectedTags = this.state.selectedTags;
if (selectedTags.includes(id)) {
var index = selectedTags.indexOf(id)
selectedTags.splice(index, 1);
} else {
selectedTags.push(id);
}
console.log("id****************selectedTags", selectedTags);
this.setState({
selectedTags: selectedTags
})
},
render: function () {
assignStates: function (note, token, tagCategories) {
let fields = [];
fields["title"] = note.title_en;
fields["body"] = note.body_en;
let selectedFileName = null
if (note.file_url_en != "") {
console.log("note.file_url_en ", note.file_url_en);
selectedFileName = note.file_url_en
}
let selectedTags = [];
let n = 0;
(note.note_tag).forEach(tag => {
selectedTags.push(tag.id.toString());
n++;
});
console.log("id****************first", selectedTags);
let initial_values = {
note: note,
id: note.id,
api: new Api(token),
message: "",
title: note.title_en,
body: note.body_en,
fields: fields,
isEdit: false,
selectedTags: selectedTags,
tagCategories: tagCategories,
selectedFileName: selectedFileName,
}
return initial_values;
},
const { selectedTags } = this.state;
{(tagCategory.tags).map((tag) => (
<div className="col-3">
<div>
<input
type="checkbox"
value={selectedTags.includes(tag.id)}
id={tag.id}
onChange={this.addTags} />
<label style={{ marginLeft: "10px", fontSize: "15px" }}>
{tag.name_en}
</label>
</div>
</div>
))
}
})
Image related for the problem
You've an issue with state mutation. You save a reference to the current state, mutate it, and then save it back into state. This breaks React's use of shallow reference equality checks during reconciliation to determine what needs to be flushed to the DOM.
addTags(e) {
let id = e.target.id;
let selectedTags = this.state.selectedTags; // reference to state
if (selectedTags.includes(id)) {
var index = selectedTags.indexOf(id)
selectedTags.splice(index, 1); // mutation!!
} else {
selectedTags.push(id); // mutation!!
}
this.setState({
selectedTags: selectedTags // same reference as previous state
});
},
To remedy you necessarily return a new array object reference.
addTags(e) {
const { id } = e.target;
this.setState(prevState => {
if (prevState.selectedTags.includes(id)) {
return {
selectedTags: prevState.selectedTags.filter(el => el !== id),
};
} else {
return {
selectedTags: prevState.selectedTags.concat(id),
};
}
});
},
Use the "checked" attribute.
<input
type="checkbox"
value={tag.id}
checked={selectedTags.includes(tag.id)}
id={tag.id}
onChange={this.addTags} />
also, about the value attribute in checkboxes:
A DOMString representing the value of the checkbox. This is not displayed on the client-side, but on the server this is the value given to the data submitted with the checkbox's name.
Note: If a checkbox is unchecked when its form is submitted, there is
no value submitted to the server to represent its unchecked state
(e.g. value=unchecked); the value is not submitted to the server at
all. If you wanted to submit a default value for the checkbox when it
is unchecked, you could include an inside the
form with the same name and value, generated by JavaScript perhaps.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#value
I think you should use checked property instead of value.
For reference check react js docs here
You are mutating state variable directly with selectedTags.splice(index, 1); and selectedTags.push(id);
What you need to do is make a copy of the state variable and change that:
addTags(e) {
let id = e.target.id;
if (this.state.selectedTags.includes(id)) {
this.setState(state => (
{...state, selectedTags: state.selectedTags.filter(tag => tag !== id)}
))
} else {
this.setState(state => (
{...state, selectedTags: [...state.selectedTags, id]}
))
}
}

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

Updating state with new value to the object of array is really slow

I have a data set much larger than the example below. I'm trying to add notes to the profiles, however, there is a relatively large lag between typing and the text appearing in the textarea. I assume this happening because it has to go through a lot of data. Is there a more efficient way to do this.
Data
const profiles = [{
firstName: 'Brady',
lastName: 'Smith'
}, {
firstName: 'Jason',
lastName: 'Brady'
}, {
firstName: 'Michael',
lastName: 'Bolten'
}];
Component
class Notes extends React.Component {
constructor(props) {
super(props);
this.state = {
profiles: []
};
this.handleAddingUserNotes = this.handleAddingUserNotes.bind(this);
}
handleAddingUserNotes(e, userId) {
const { profiles } = this.state;
const addNotesToProfiles = profiles.map(profile => {
if (profile.userId === userId) {
profile.notes = e.target.value;
}
return profile;
});
this.setState({ profiles: addNotesToProfiles });
}
render() {
const { profiles } = this.state;
return (
<div>
{profiles.map(profile => {
const { userId, notes } = profile;
return (
<textarea
className="form-control"
value={notes}
onChange={e => this.handleAddingUserNotes(e, userId)}
/>
);
})}
</div>
);
}
}
You can pass the index of the profile to update in the change handler and then directly update that profile.
// in your render method
{profiles.map((profile, index) => {
const { userId, notes } = profile;
return (
<textarea
className="form-control"
value={notes}
onChange={e => this.handleAddingUserNotes(e, index, userId)}
/>
);
})}
// in your handle change
handleAddingUserNotes(e, index, userId) {
const { profiles } = this.state;
const newProfiles = [...profiles.slice(0, index), {...profiles[index], notes: e.target.value}, ...profiles.slice(index+1)]
this.setState({ profiles: newProfiles });
}
Or, if you anyways have unique userIds for each user, you can convert the whole array into an object where each user is indexed by the userId and then directly update that user in the userProfiles object. O(1) operation.
Edit:
And yes, as the other answer mentions, please use key with each of your textarea so that react does not rerender every text area on every render.

Resources