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)
]
})
}
Related
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}
}, {});
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
});
I have an array with below list of items as shown in image , I would like to remove the duplicates
[L7-LO, %L7-LO] from that array.
I have tried with the following conditions:
Scenario 1 :
this.formulalist.filter((el, i, a) => i == a.indexOf(el))
Scenario 2:
Observable.merge(this.formulalist).distinct((x) => x.Value)
.subscribe(y => {
this.formulalist.push(y)
});
Scenario 3:
this.formulalist.forEach((item, index) => {
if (index !== this.formulalist.findIndex(i => i.Value == item.Value))
{
this.formulalist.splice(index, 1);
}
});
None of the three scenarios above were able to remove the duplicates from that array. Could any one please help on this query?
angular is not necessary use vanillajs
filter the elements with only one occurrence and add to the new list the first occurrence
let newFormulalist = formulalist.filter((v,i) => formulalist.findIndex(item => item.value == v.value) === i);
Try populating a new array without duplicates. Assign the new array later to formulalist.
newArr = []
this.formulalist.forEach((item, index) => {
if (this.newArr.findIndex(i => i.Value == item.Value) === -1)
{
this.newArr.push(item)
}
});
this.formulalist = this.newArr
EDIT
Looking at the answer above, the solution seems so outdated. A better approach would have been to use an Array.filter() than a Array.forEach().
But, having a better solution would be nice, now when I see this question, I feel findIndex() not to be a good approach because of the extra traversal.
I may have a Set and store the values in the Set on which I want to filter, If the Set has those entries, I would skip those elements from the array.
Or a nicer approach is the one that is used by Akitha_MJ, very concise. One loop for the array length, an Object(Map) in the loop with keys being the value on which we want to remove duplicates and the values being the full Object(Array element) itself. On the repetition of the element in the loop, the element would be simply replaced in the Map. Later just take out the values from the Map.
const result = Array.from(this.item.reduce((m, t) => m.set(t.name, t), new Map()).values());
Hope this works !!
// user reduce method to remove duplicates from object array , very easily
this.formulalist= this.formulalist.reduce((a, b) => {
if (!a.find(data => data.name === b.name)) {
a.push(b);
}
return a;
}, []);
// o/p = in formulalist you will find only unique values
Use a reducer returning a new array of the unique objects:
const input = [{
value: 'L7-LO',
name: 'L7-LO'
},
{
value: '%L7-LO',
name: '%L7-LO'
},
{
value: 'L7-LO',
name: 'L7-LO'
},
{
value: '%L7-LO',
name: '%L7-LO'
},
{
value: 'L7-L3',
name: 'L7-L3'
},
{
value: '%L7-L3',
name: '%L7-L3'
},
{
value: 'LO-L3',
name: 'LO-L3'
},
{
value: '%LO-L3',
name: '%LO-L3'
}
];
console.log(input.reduce((acc, val) => {
if (!acc.find(el => el.value === val.value)) {
acc.push(val);
}
return acc;
}, []));
if you are working using ES6 and up, basic JS using map and filter functions makes it easy.
var array = [{value:"a"},{value:"b"},{value:"c"},{value:"a"},{value:"c"},{value:"d"}];
console.log(array.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj["value"]).indexOf(obj["value"]) === pos;
}));
Filtering for unique values is much faster with assigning values to some object properties - there not will be duplicates.
This approach gets better and better with every +1 member of initial array, because looping will be causing fast algorithm complications
let arr = [
{value: 'L7-LO', name: 'L7-LO'},
{value: '%L7-LO', name: '%L7-LO'},
{value: 'L7-LO', name: 'L7-LO'},
{value: '%L7-LO', name: '%L7-LO'},
{value: 'L7-L3', name: 'L7-L3'},
{value: '%L7-L3', name: '%L7-L3'},
{value: 'LO-L3', name: 'LO-L3'},
{value: '%LO-L3', name: '%LO-L3'}
];
let obj = {};
const unique = () => {
let result = [];
arr.forEach((item, i) => {
obj[item['value']] = i;
});
for (let key in obj) {
let index = obj[key];
result.push(arr[index])
}
return result;
}
arr = unique(); // for example;
console.log(arr);
I am trying to update the property of an object which is stored in an array.
my state looks something like this:
state = {
todos: [
{
id: '1',
title: 'first item,
completed: false
},
{
id: '2',
title: 'second item,
completed: false
}
],
}
What I am trying to do is access the second element in the 'todos' array and update the completed property to either false -> true or true -> false.
I have a button with the handler for update, and my class method for the update looks like this:
onUpdate = (id) => {
const { todos } = this.state;
let i = todos.findIndex(todo => todo.id === id);
let status = todos[i].completed
let updatedTodo = {
...todos[i],
completed: !status
}
this.setState({
todos: [
...todos.slice(0, i),
updatedTodo,
...todos.slice(i + 1)
]
});
}
While this does work, I want to find out if there is a more concise way of achieving the same result; I tried to use Object.assign(), but that didn't work out because my 'todos' is an array, not an object. Please enlighten me with better code!
It would be best to use update function to make sure you don't work on outdated data:
onUpdate = (id) => {
this.setState(prevState => {
const copy = [...prevState.todos];
const index = copy.findIndex(t => t.id === id);
copy[index].completed = !copy[index].completed;
return { todos: copy }
})
}
You can simply copy your todos from state, then make edits, and after that put it back to the state
onUpdate = (id) => {
var todos = [...this.state.todos]
var target = todos.find(todo => todo.id == id)
if (target) {
target.completed = !target.completed
this.setState({ todos })
}
}
I'm using immutability-helper for doing CRUD operations on state data and want to know if I should always use $splice for removing data or is it ok to use filter (since it's not destructive)?
For example, let's say I have an array of objects:
todos = [
{id: 1, body: "eat"},
{id: 2, body: "drink"},
{id: 3, body: "sleep"},
{id: 4, body: "run"}
]
Given an item id, I can remove it in two ways:
a. find its index and use $splice:
index = todos.findIndex((t) => { return(t.id === id) });
newtodos = update(todos, { $splice: [[index, 1]] })
OR
b. use filter:
newtodos = todos.filter((t) => { return(t.id === id) });
filter is more concise but I'm not sure if it has any disadvantages compared to using $splice in this case.
use immutability-helper:
it's convenient to process nested collection:
const collection = [1, 2, { todos: [...todos] }];
const newCollection = update(collection, {
2: {
todos: {
$apply: todos => todos.filter(t => t.id !== id)
}
}
});
and, it give you a new copy for collection and collection[2]:
console.log(newCollection === collection, newCollection[2] === collection[2]);
//false false
So, if you use react-redux, connect state to component, if you want your component re-render when the state changed, you must return a new copy of state.
Do this operator with old way:
const todoList = collection[2].todos;
const idx = todoList.findIndex(t => t.id === id);
const newTodoList = update(todoList, { $splice: [[index, 1]] });
const newCollectionTwo = [...collection];
newCollectionTwo[2] = {
todos: newTodoList
};
and take a look with console:
console.log(collection, newCollectionTwo, collection === newCollectionTwo, collection[2] === newCollectionTwo[2]);
for simple data structure and operator, i think it's equal with filter.
Sorry for my English is not good, and this is my opinion.