How to update dynamic changing fields of json objects using React setState? - reactjs

I have an object that its fields change dynamically, e.g.,
var Obj = {f1:"", f2:""} or var Obj = {f1:"", f2:"", f3:"" } etc
An input field appears dynamically on screen for every field of the object.
I want to set the state of the object with the values that users enter in each field. How can I do this? I have tried the following code but it doesn't always works correctly.
for (var key in this.state.Obj) {
if (this.state.Obj.hasOwnProperty(key)) {
this.setState({
Obj: update(this.state.Obj, {[key]: {$set: window.$('[name='+key+']')[0].value}}),
})
}
}

As mentioned, you should use an onChange on whatever input fields you need. You could do something like:
<input
type="text"
value={this.state.Obj.f1}
onChange={(e) => {
// Make sure you keep values from other fields
var object = this.state.Obj;
// Modify the value on the object
object.f1 = e.target.value;
// Update the state
this.setState({ Obj: object });
}}
/>

Related

React useState not updating mapped content

I feel like im missing something that is staring me right in the face. I am trying to have content stored in an array of objects update when a checkbox is on or off. The console log is showing the object data is updating correctly so I assume my fault resides in not understanding useState fully?
const [statistics, setStatistics] = useState([
{
id: 1,
content: <div>Content1</div>,
state: true,
},
{
id: 2,
content: <div>Content2</div>,
state: true,
},
]);
In the component:
{statistics.map((item) => (item.state ? item.content : <></>))}
<input
type="checkbox"
onChange={(e) => {
let newArr = statistics;
e.target.checked
? (newArr[0].state = true)
: (newArr[0].state = false);
setStatistics(newArr);
console.log(statistics);
}}
/>
You are trying to change the state directly, instead you need to work with a copy of the state and make all changes to it.
Just replace in your code this string:
let newArr = statistics; // as link to base array
to
let newArr = [...statistics]; // as new copy of base array
and it will works.
React skips all state changes if they are made directly.
To create a new array as copy/clone of another array, in ES6, we can use the spread operator. You can not use = here, since it will only copy the reference to the original array and not create a new variable. Just read here for reference.
In your case, your newArray will refer to the old statistics and will not be detected as the new state. That is why no re-render takes place after you made changes to it.
So here, you can do this:
return (
<>
{statistics.map((item) => (item.state ? item.content : <></>))}
<input
type="checkbox"
onChange={(e) => {
setStatistics((prevStats) => {
const newStats = [...prevStats];
e.target.checked
? (newStats[0].state = true)
: (newStats[0].state = false);
return newStats;
});
}}
/>
</>
);

React array state update

I am getting a book list from database and is stored in a state variable Book list also has book price field
const [books, setBooks]=useState([])
setBooks(data)
Each books object in the array has properties like BookName, Author , Price, Discount
I have rendered a html table like follows
return ( <div>
{books.map((x,i) => ( <tr>
<td>x.bookName</td>
<td>x.price</td>
<td><MyCustomTextInput onChange={e => handleChange(e, x.id)} value={x.discount}></MyCustomTextInput></td>
<tr></div>);
the sample code for MyCustomTextInput is as follows
function MyCustomTextInput(props)
{ return (<div><TextInput></TextInput> </div>)
} exports default MyCustomTextInput
The code where I update the price for corresponding object in "books" array is as follows
function handleChange(x,id){
var obj = books[id];
obj.price = obj.price - e.target.value; //I am correctly getting discount in e.target.value
}
Every thing works properly except the price after discount is not reflecting on the UI. though its getting updated properly in the array but not reflecting on the UI.
any help....
Experts -
This is setting a value:
function handleChange(x, id){
var obj = books[id];
obj.price = obj.price - e.target.value;
}
But it's not updating state. The main rule to follow here is to never mutate state directly. The result of bending that rule here is that this update never tells React to re-render the component. And any re-render triggered anywhere else is going to clobber the value you updated here since state was never updated.
You need to call setBooks to update the state. For example:
function handleChange(x, id){
setBooks(books.map(b =>
b.id === id ? { ...b, price: b.price - parseFloat(e.target.value) } : b
));
}
What's essentially happening here is that you take the existing array in state and use .map() to project it to a new array. In that projection you look for the record with the matching id and create the new version of that record, all other records are projected as-is. This new array (the result of .map()) is then set as the new updated state.
There are of course other ways to construct the new array. You could grab the array element and update it the way you already are and then combine it with a spread of the result of a .filter to build the new array. Any construction which makes sense to you would work fine. The main point is that you need to construct a new array and set that as the new state value.
This will trigger React to re-render the component and the newly updated state will be reflected in the render.
You need to setBooks to update state books.
function handleChange(x, id) {
setBooks(
books.map((item) =>
item.id === id ? { ...item, price: item.price - parseFloat(e.target.value) } : item,
),
);
}
To achieve that, you need to call setBooks after changing the price within handleChange method to re-render the component with the newly updated state.
It's simply like the following:
function handleChange(x,id){
var obj = books[id];
obj.price = obj.price - e.target.value; //I am correctly getting discount in e.target.value
setBooks([...books]);
}

Multiple Select not working, only giving last clicked value

I implemented a multiple select dropdown from react-bootstrap documentation.
It does not let me do multiple select and only gets the last clicked option. I have state variable set to array. What else am I missing? App is created with create-react-app.
I have state set to array inside the class constructor. Binding of event handler also done in the constructor.
Next, I'm showing my event handler followed by form group with onChange and value set to state. (note I have a drop-down above this which is working fine.)
I then pass this value to a few classes before it's parsed to JSON. The last pastes are those classes. I have removed other parameters so easier to read, any ideas, feel free to ask for more info.
this.state = {
codeCoverage: [],
}
this.handleCodeCoverageChange = this.handleCodeCoverageChange.bind(this);
//Event handlers below
handleCodeCoverageChange(event){
this.setState({
codeCoverage: event.target.value
})
}
<Form.Group>
<Form.Label>Please choose your desired code coverage software(s)</Form.Label>
<Form.Control as="select" value={this.state.codeCoverage} onChange={this.handleCodeCoverageChange} multiple>
<option value="">--Please choose an option--</option>
<option value="cobertura">Cobertura</option>
<option value="sonarcube">Sonarcube</option>
</Form.Control>
</Form.Group>
var configurator = new Configurator(this.state.codeCoverage)
class Configurator
{
constructor(
code_coverage)
{
this.pipeline = new Pipeline(code_coverage)
}
}
class Pipeline
{
constructor(code_coverage)
{
this.analysisAndSecurity = new AnalysisAndSecurity(code_coverage)
}
class AnalysisAndSecurity{
parameter
constructor(code_coverage)
{
this.code_coverage = code_coverage
}
}
In your handleChange function you assign state.codeCoverage the value of the selected element instead of adding it to the array of selected element. This is why when you select another element it deletes the old value. I would recommend logging e.target.value and this.state.codeCoverage to better understand. As for the solution:
Since you are using multiple select it expects an array as value instead of a single value. So you need to change two things in your handleChange method.
First you need to add your element to existing values and not replace them.
You need to handle when a selected element is clicked again and needs to become unselected.
You can do both these tasks as shown below:
handleChange = e => {
const { codeCoverage } = this.state;
// Find the value selected the codeCoverage array
const index = codeCoverage.indexOf(e.target.value);
// If the value is not found then add it to the array
if (index === -1) codeCoverage.push(e.target.value);
// If value found then remove the value to unselect
else codeCoverage.splice(index, 1);
// Set the state so that the component reloads with the new value
this.setState({ codeCoverage: [...codeCoverage] });
};

Angular 6 dynamic checkboxes keyvalue return array of IDs

I have a list of dynamic checkboxes using keyvalue pipe that returns objects rather than just array of selected IDs.
can anyone help pls, I need the form to submit just an array of selected user IDs.
https://stackblitz.com/edit/angular-ciaxgj
EDIT
here's the log of a similar form with a multi-select (countries):
console log
I need users (checkboxes) to return an array like countries (multi-select) as in the log above.
Change your OnSubmit function to
onSubmit() {
console.log(this.userForm.value);
var usersObj = this.userForm.value.users;
var selectedUserIds = [];
for (var userId in usersObj) {
if (usersObj.hasOwnProperty(userId)) {
if(usersObj[userId])//If selected
{
selectedUserIds.push(userId);
}
}
}
console.log(selectedUserIds);
}
Here is updated code
https://stackblitz.com/edit/angular-9fd17t
If you want to submit as a Form then add a hidden field to form
<input type="hidden" name="selectedUserIds" value=""/>
set value selectedUserIds from onSubmit and submit the form code.
So, it happens that I have been overthinking the solution in thinking that like the multiselect, checkboxes would/could return a ready array of selected items.
The rather simple solution was from reading another of Eliseo's posts combined with Jaba Prince's answer:
onSubmit() {
var usersObj = this.userForm.value.users;
var selectedUserIds = [];
for (var userId in usersObj) {
if (usersObj.hasOwnProperty(userId)) {
if(usersObj[userId])//If selected
{
selectedUserIds.push(userId);
}
}
}
let data = {
users: selectedUserIds
}
console.log(data);
// post data in service call
}
Many thanks to you two!

Updating a nested state setState in react

Cannot figure out why this state will not update, there are other values other than personalValues that is why it is nested.
The state
this.state = {
personalValues: [
{targetLevel: 0},
{firstCommission: 0.36},
{cancels: 0.10},
{averagePolicy: 1150},
{productionWeeks: 48},
{presPerWeek: 15},
{closingRate: 0.40},
{appsPerWeek: 6}
]
The handler I tried
handleFormChange(event){
this.setState({[event.target.name]: event.target.value})
}
The Component
const personalFormValues =
{
class: ["firstCommission", "cancels", "averagePolicy",
"productionWeeks", "presPerWeek", "closingRate",
"appsPerWeek"],
};
var className = personalFormValues.class[i];
<TextField
className={className}
type="number"
placeholder={personalFormValues.placeholder[i]}
onChange={this.handleFormChange}
onBlur={this.changeValues}
name={className}
fullWidth={true}
pattern="[0-9]*"
required />
In terms of the state there is only a single value this.state.personalValues. To React it doesn't matter if that value is an array of objects. Instead simply create a variable that stores the current value let obj = this.state.personalValues. Then iterate through obj until you find a match on the key with event.target.name and set the value of that obj to event.target.value. Or more simply doesn't use an array and use an object directly:
this.state = {
personalValues: {
targetLevel: 0,
firstCommission: 0.36,
cancels: 0.10,
averagePolicy: 1150,
productionWeeks: 48,
presPerWeek: 15,
closingRate: 0.40,
appsPerWeek: 6
}
Then you could just do:
let obj = this.state.personalValues;
obj[event.target.name] = event.target.value;
this.setState({personalValues: obj});
React checks elements of this.state for shallow pointer equivalency before triggering a re-render.
Therefore, if you in-place-modify a recursive data-structure, then React will fail to notice a change, because shallow comparison between previous and next state object shows no difference.
Thus you cannot write to a recursive structure, nor do:
this.setState(prevState => ({personalValues: Object.assign(prevState.personalValues, nextValues)}));
because Object.assign performs in-place modification.
You must do either of:
this.setState(prevState => ({personalValues: Object.assign({}, prevState.personalValues, nextValues)}));
this.setState(prevState => ({personalValues: {...prevState.personalValues, ...nextValues}}));
This will create a new object and copy the data-structure in it. Be aware that only the first level object is created anew.
Recursive copying gets ugly; see https://stackoverflow.com/a/43041334/1235724
Nonetheless, if you have a very large data structure stored in your state,
you may want to use in-place modification together with this.forceUpdate(),
eschewing copying altogether.
Do you need personalValues to be an array? If you specify it like this, that should work:
state = {
personalData: {
targetLevel: 0,
firstCommission: 0.36
....
}
}
handleFormChange({target}){
this.setState(prevState => ({
personalValues: {
...prevState.personalValues,
[target.name]: target.value
}
})
}
Better way to copy old state to a new variable then change the values if you want and then update the state value with that newly created type variable.
let personalValuesCopy = this.state.personalValues;
personalValues.dynamicKey = updated values // Update values may be using onChange events or some custom data
this.setState({ personalValues : personalValuesCopy }) // Update state with new object

Resources