Modifying nested state fails to update - javascript

Why setState is not working for me?
On change event, I am setting a state for array.
handleonChange(x) {
var newArray = ['Hello', 'Dear'];
const clonedState = Object.assign({}, this.state);
clonedState.trans.value = x;
clonedState.accList = newArray
this.setState(clonedState);
}
It updates the trans.value but accList does not set.

why not just try setting state with this syntax (this way you won't have to clone the object, just declare how you want the state to be mutated)
this.setState(previousState => {
trans:
{
...previousState.trans,
value: x,
},
accList: newArray
});

Related

Updating nested state array react [duplicate]

If you have an array as part of your state, and that array contains objects, whats an easy way to update the state with a change to one of those objects?
Example, modified from the tutorial on react:
var CommentBox = React.createClass({
getInitialState: function() {
return {data: [
{ id: 1, author: "john", text: "foo" },
{ id: 2, author: "bob", text: "bar" }
]};
},
handleCommentEdit: function(id, text) {
var existingComment = this.state.data.filter({ function(c) { c.id == id; }).first();
var updatedComments = ??; // not sure how to do this
this.setState({data: updatedComments});
}
}
I quite like doing this with Object.assign rather than the immutability helpers.
handleCommentEdit: function(id, text) {
this.setState({
data: this.state.data.map(el => (el.id === id ? Object.assign({}, el, { text }) : el))
});
}
I just think this is much more succinct than splice and doesn't require knowing an index or explicitly handling the not found case.
If you are feeling all ES2018, you can also do this with spread instead of Object.assign
this.setState({
data: this.state.data.map(el => (el.id === id ? {...el, text} : el))
});
While updating state the key part is to treat it as if it is immutable. Any solution would work fine if you can guarantee it.
Here is my solution using immutability-helper:
jsFiddle:
var update = require('immutability-helper');
handleCommentEdit: function(id, text) {
var data = this.state.data;
var commentIndex = data.findIndex(function(c) {
return c.id == id;
});
var updatedComment = update(data[commentIndex], {text: {$set: text}});
var newData = update(data, {
$splice: [[commentIndex, 1, updatedComment]]
});
this.setState({data: newData});
},
Following questions about state arrays may also help:
Correct modification of state arrays in ReactJS
what is the preferred way to mutate a React state?
I'm trying to explain better how to do this AND what's going on.
First, find the index of the element you're replacing in the state array.
Second, update the element at that index
Third, call setState with the new collection
import update from 'immutability-helper';
// this.state = { employees: [{id: 1, name: 'Obama'}, {id: 2, name: 'Trump'}] }
updateEmployee(employee) {
const index = this.state.employees.findIndex((emp) => emp.id === employee.id);
const updatedEmployees = update(this.state.employees, {$splice: [[index, 1, employee]]}); // array.splice(start, deleteCount, item1)
this.setState({employees: updatedEmployees});
}
Edit: there's a much better way to do this w/o a 3rd party library
const index = this.state.employees.findIndex(emp => emp.id === employee.id);
employees = [...this.state.employees]; // important to create a copy, otherwise you'll modify state outside of setState call
employees[index] = employee;
this.setState({employees});
You can do this with multiple way, I am going to show you that I mostly used. When I am working with arrays in react usually I pass a custom attribute with current index value, in the example below I have passed data-index attribute, data- is html 5 convention.
Ex:
//handleChange method.
handleChange(e){
const {name, value} = e,
index = e.target.getAttribute('data-index'), //custom attribute value
updatedObj = Object.assign({}, this.state.arr[i],{[name]: value});
//update state value.
this.setState({
arr: [
...this.state.arr.slice(0, index),
updatedObj,
...this.state.arr.slice(index + 1)
]
})
}

.map not is not a function on empty object?

I am new to react. I have set up a reducer with the state as an empty object. But when using the .map() function it doesn't seem to work on the object? Does the .map only work on arrays?
export const orders = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case "NEW_ORDER":
const { new_order } = payload;
const new_state = { ...state, new_order };
console.log(new_state);
return new_state;
}
return state;
}
Well, no, you can not use .map() on an object since it is supposed to be used in an array. By the way, if you are trying to store a list of orders, you should use am array instead, so initialize your state with [] and not with {}, or with a key that actually contains your orders like { orders: [] } and then add the order you received like const new_state = { ...state, orders: [...state.orders, new_order] }.
You are correct. The map function is part of the Array prototype.
See here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
However, I don't see a map function in the code sample you posted?
If you want to loop over an object though you can use Object.entries.
const myObj = {foo: "bar"}
const result = Object.entries(myObj).map(([key, value]) => {
console.log(key) // "foo"
console.log(value) // "bar"
return `${key}-${value}`
})
console.log(result) // ["foo-bar"]

How to map array to new single object in Angular 8?

I'm doing this:
const rawValues = this.filterList.map(s => {
return {[s.filterLabel]: s.selectedOption}
});
filterList variable has this type:
export interface SelectFilter {
filterLabel: string;
options: Observable<any>;
selectedOption: string;
}
now rawValues is being mapped like this:
[
{filterLabel: selectedOption},
{filterLabel: selectedOption},
{filterLabel: selectedOption}
]
so it's an array of my new objects,
but what I want is a SINGLE object, so the end result should be:
{
filterLabel: selectedOption,
filterLabel: selectedOption,
filterLabel: selectedOption
}
NOTE that "filterLabel" will always be unique.
What do I need to change in the map() ?
For this use case, a map isn't needed as it would result in creating a new array which is unnecessary. Just iterate over each element in the array then assign each filterLabel as a new key to the obj like this:
const obj = {};
this.filterList.forEach(s => {
obj[s.filterLabel] = s.selectedOption;
});
console.log(obj);
I think this is use case for array reduce:
let result =
[{filterLabel: 'label1', selectedOption: 'option1'}, {filterLabel: 'label2', selectedOption: 'option2'}, {filterLabel: 'label3', selectedOption: 'option3'}, {filterLabel: 'label4', selectedOption: 'option4'} ]
.reduce(function(previousValue, currentValue, index, array) {
return {
[currentValue.filterLabel]: currentValue.selectedOption,
...previousValue }
}, {});
console.log(result);
More details:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
You shouldn't do anything to get the result you want. First, when you run a map on the array, a new array is returned. To change that you would have to re-write the map function with your own. Technically possible, but not recommended.
Second, you cannot have multiple properties on an object that have the exact same name. I know of no way around this.
You might be able to do something like what you want with a loop:
let rawValues = {};
for (i = 0; i < filterList.length; i++) {
rawValues[`${filterList[i].filterLabel}${i}`] = filterList[i].selectedOption;
}
Which should give you something like this:
{
filterLabel1: selectedOption,
filterLabel2: selectedOption,
filterLabel3: selectedOption
}
Can you guarantee that the filterLabel will always be unique?
var result = {};
this.filterList.forEach(s => {
result[s.filterLabel] = s.selectedOption;
});
you can use reduce to achieve the same result:
var result = this.filterList.reduce((prev, next) => {
return {...prev, [next.filterLabel]:next.selectedOption}
}, {});

Which approach in React is better?

Below both code does exactly same but in different way. There is an onChange event listener on an input component. In first approach I am shallow cloning the items from state then doing changes over it and once changes are done I am updating the items with clonedItems with changed property.
In second approach I didn't cloned and simply did changes on state items and then updated the state accordingly. Since directly (without setState) changing property of state doesn't call updating lifecycles in react, I feel second way is better as I am saving some overhead on cloning.
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const clonedItems = Array.from(items);
clonedItems.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items: clonedItems });
};
OR
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items });
};
You can use this
this.setState(state => {
const list = state.list.map(item => item + 1);
return {
list,
};
});
if you need more info about using arrays on states, please read this: How to manage React State with Arrays
Modifying the input is generally a bad practice, however cloning in the first example is a bit of an overkill. You don't really need to clone the array to achieve immutability, how about something like that:
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const processedItems = items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
return {
...ele,
rate: Number(value)
};
} else {
return ele;
}
});
this.setState({ items: processedItems });
};
It can be refactored of course, I left it like this to better illustrate the idea. Which is, instead of cloning the items before mapping, or modifying its content, you can return a new object from the map's callback and assign the result to a new variable.

Replace whole object in react state

Is there a way to update the state with a state structure like this
this.state = {
structure: [
{
selected: false,
name: "a",
key: "a",
}, {
selected: false,
name: "b",
key: "b"
}, {
selected: false,
name: "c",
key: "c",
}, {
selected: false,
name: "d",
key: "d"
}
]
}
I want to update the state. I am doing it this way:
_onPress = (obj, index) => {
const oldStateSelected = obj.selected;
const newStateObject = Object.assign({}, obj);
newStateObject.selected = !oldStateSelected;
const oldState = _.cloneDeep([...this.state.structure]);
oldState.splice(index, 1);
const newState = oldState.push(newStateObject)
this.setState({
structure: [newState]
});
}
However, that returns me a new state of
{ structure: [4] }
I think the problem is, that I am modifiing the state in place instead of replacing it?!
When I console.log(oldState) after removing the element from the array, I see that it says oldState (3) [Object, Object, Object].
But when I open it, there are 4 array elements. The element I wanted to remove with splice is still in there.
Any ideas?
Problem is in this line:
const newState = oldState.push(newStateObject);
array.push will not return the updated array, when we use push it will update the original array, so you need to assign the variable oldState value to state variable structure.
Use this:
_onPress = (obj, index) => {
const oldStateSelected = obj.selected;
const newStateObject = Object.assign({}, obj);
newStateObject.selected = !oldStateSelected;
const oldState = _.cloneDeep([...this.state.structure]);
oldState.splice(index, 1);
oldState.push(newStateObject)
this.setState({
structure: oldState //here
});
}
Check this snippet:
let a = [10,15,20];
let b = a.push(20);
console.log('a = ', a);
console.log('b = ', b);
Array.prototype.push is not returning the array. Just push and do
this.setState({
structure: oldState
});

Categories

Resources