I have an array like this:
a=[{id:1 , operand:2 ,children:[{a:1]{b:2}]
And I do this when I want to add a new field:
const [items, setItems] = useState(a);
const gettableData = (value) => {
let operanList = items;
let repeatListError = tableData.filter((i) => i.operandId === value.operandId);
if (!repeatListError.length > 0) operanList.push(value);
setTableData(operanList);
};
This method is called every time I click a button.
I want to check operand when I add a new object, if there is only update children.like:
value=[{id:1 , operand:2 ,children:[{a:1]{b:2}{c:3}{d:4}]
I see some inconsistencies in the variable names used in your code, e.g. where is tableData? Is it the items which is part of state?
I don't understand if children is an array of objects or just an object with key value pairs.
Anyway, as I mentioned in my comment, the easiest way to achieve this is using map. Consider it to be a generic implementation and try to use it in your code.
const a=[{id:1 , operand:2 ,children:[{a:1]{b:2}] // What is the data stucture of children? Is it array of objects or just object?
const [items, setItems] = useState(a);
const gettableData = (value) => {
// creating a copy is not required as map will return a new array.
// map => filter + modify, so use map
let updatedList = tableData.map((i) => {
if (i.operandId === value.operandId) {
// match found, so update children
return {
...i, // first, copy everything from i
children: [ // then update children, and since children is an array of objects
...i.children, // first copy every key of i's children
...value.children // then copy value's children
]
}
}
return i; // no match? return i as it is.
});
setItems(updatedItems)
First reformat value like the following.What you have is incorrect in syntax:
const value=[{id:1 , operand:2 ,children:{a:1,b:2,c:3,d:4}}]
This how you can change the children inside the objects that are in your array:
const list = [
{ id: 1, operand: 2, children: { a: 1, b: 2, c: 3, d: 4 } },
{ id: 2, operand: 3, children: { a: 1, b: 2, c: 3, d: 4 } },
];
let newList = [...list];
let updatedChildren = { ...newList[1].children, e: 5, f: 6 };
let updatedItem = { ...newList[1], children: updatedChildren };
newList.splice(1, 1, updatedItem);
console.log(newList);
setState(newList);
you create a new instance of the array. update the children attribute of your desired item ( in this case i chose 1 ). then update the entire object that is in your array ( it contains the id & operand and children ). and finally mutate the newList with replacing the original item by the new one. In the splice method remember the second number must always be one, as it will remove one item and the first number given should be the index of the item that is needed to be changed ( in this case we chose one). then you can continue with setting the state as you wish since you have a new, updated list.
You have to put the array in another variable and tell it that if it has children, it will remember it in the children field and set a new value for it.
Related
I've got a counter array, containing 3 objects.
const [counter, setCounter] = useState([
{ id: 0, count: [] },
{ id: 1, count: [] },
{ id: 2, count: [] }
])
Theres then 3 buttons that when pressed call the following function.
const update = (i, text) => {
setCounter(currCount =>
currCount.id === i
? { id: i, count: [...counter[i].count, text] }
: currCount
);
};
The buttons pass "i" which is 0,1,2 corresponding to the 3 object ids and "text" which is the error text.
The function should update the specific object from the array adding the new error text to that id's count array.
I cant seem to get this to work though, it keeps returning undefined.
Any help is appreiciated.
The useState dispatch function (setCounter in your case) replaces the whole state with the value it is provided with.
In your example, you need to recreate the whole array like so:
const update = (i, text) => {
setCounter(currCounter =>
[
...currCounter.filter(count => count.id !== i), // the old counter state without the one we want to change
{ id: i, count: [...currCounter[i].count, text] }
]
);
};
For the following code block:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
item = {
...item,
...changes
}
})
console.log(items) // items NOT reassigned with changes
items.forEach((item, i) => {
items[i] = {
...item,
...changes
}
});
console.log(items) // items reassigned with changes
Why does reassigning the values right on the element iteration not change the objects in the array?
item = {
...item,
...changes
}
but changing it by accessing it with the index does change the objects in the array?
items2[i] = {
...item,
...changes
}
And what is the best way to update objects in an array? Is items2[i] ideal?
Say no to param reassign!
This is a sort of a fundamental understanding of higher level languages like JavaScript.
Function parameters are temporary containers of a given value.
Hence any "reassigning" will not change the original value.
For example look at the example below.
let importantObject = {
hello: "world"
}
// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
parameter = {
...parameter,
updated: "object"
}
}
tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);
As you can see when you re-assign a parameter the original value will not be touched. There is even a nice Lint rule since this is a heavily bug prone area.
Mutation will work here, but ....
However if you "mutate" the variable this will work.
let importantObject = {
hello: "world"
}
// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
parameter["updated"] = "object"
}
tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);
So coming back to your code snippet. In a foreach loop what happens is a "function call" per each array item where the array item is passed in as a parameter. So similar to above what will work here is as mutation.
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach((item, i) => {
// Object assign just copies an object into another object
Object.assign(item, changes);
})
console.log(items)
But, it's better to avoid mutation!
It's better not mutate since this can lead to even more bugs. A better approach would be to use map and get a brand new collection of objects.
const items = [{
id: 1,
name: 'one'
},
{
id: 2,
name: 'two'
},
];
const changes = {
name: 'hello'
}
const updatedItems = items.map((item, i) => {
return {
...item,
...changes
}
})
console.log({
items
})
console.log({
updatedItems
})
As the MDN page for forEach says:
forEach() executes the callbackFn function once for each array
element; unlike map() or reduce() it always returns the value
undefined and is not chainable. The typical use case is to execute
side effects at the end of a chain.
Have a look here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
This means that although you did create new object for item, it was not returned as a value for that index of array. Unlike your second example, the first one is not changing original array, but just creates new objects and returns undefined. This is why your array is not modified.
I'd go with a classic Object.assign for this:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
items.forEach( (item) => Object.assign(item,changes) )
console.log(items)
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
The other approach you can take is to use map and create a new array based on the original data and the changes:
const items = [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
const changes = {
name: 'hello'
}
const newItems = items.map((item) => {
...item,
...changes
})
console.log(newItems);
But if you need to modify the original array, it's either accessing the elements by index, or Object.assign. Attempting to assign the value directly using the = operator doesn't work because the item argument is passed to the callback by value not by reference - you're not updating the object the array is pointing at.
I have an array that I am trying to set the value for from a different array however my arrays are not similar the array I am trying to set should look like this
expanded = [id1, id2, id3]
while the array I am setting from looks more like this
data = [{id: 1, name:"test"},{id:2, name:"test2"},{id:3, name:"test3"}]
not sure how to do this with this.setState
const expanded = [1, 2, 3]
const data = expanded.map((e)=>(
{id: e, name: `test${e-1 ? e : '' }`}
))
// here you can use setState()
console.log(data);
const [array, setArray] = useState(data.map(ítem => item.id))
Or, if you are using clase components:
this.state = {
array: data.map(item => item.id)
}
First make your desired array from the second array. And then use that array in your state.
const data = [{id: 1, name:"test"},{id:2, name:"test2"},{id:3, name:"test3"}];
const desiredArray = data.map(item=> Object.keys(item)[0]+item.id );
console.log(desiredArray);
This code snippet will give you the array that you have mentioned you require in your project. Then setState on it accordingly.
Hello i have problem with update state in React.
const [test, setTest] = useState(
[
{
name: "example1" //i want update single el
},
{
name: "example2" //i want update single el
}
]
)
const updateTest = () => {
setTest([{ ...test[0], name: "example1 changed"}])
}
i want update element without remove another element. For the moment when i click updateTest it cause that my test[1] is removed. Why??
You should do this:
const updateTest = () => {
const newArray = [...test]; // use proper name
newArray[0].name = "new-example1";
setTest(newArray);
}
Anyway, generally you want to map, filter or whatever before updating the state, you should figure it out depending your use case. This one is kind of a test hack
You can replace the element in array and then spread it as a new array or use map in order to overwrite changes with a function.
const [test, setTest] = useState(
[
{
name: "example1" //i want update single el
},
{
name: "example2" //i want update single el
}
]
)
const updateTestSplice = () => {
let temp = ([...test])
temp.splice(0, { name: "example1 changed" })
setTest([...temp])
}
const updateTestMap = () => {
setTest(
test.map(el =>
el.name === "example1"
? { name: "example1 changed" }
: el)
)
}
What you did was you set test to a new array with only one element. If you just want to add a new element to the array, you can do this:
setTest([{ ...test[0], name: "example1 changed"}, ...test]);
and you will end up with an array with both the original elements and the added one.
If, on the other hand, you want to replace an element, you can use splice:
setTest(test.splice(0,1,{ ...test[0], name: "example1 changed"}));
where the first argument(0 in this example) is where you want to start (so in this case at the beginning of the array), next argument (1) is how many existing elements you want to remove, and the last one is the element(s) you want to add to that place in the array.
Okay, so I am trying to create a function that allows you to input an array of Objects and it will return an array that removed any duplicate objects that reference the same object in memory. There can be objects with the same properties, but they must be different in-memory objects. I know that objects are stored by reference in JS and this is what I have so far:
const unique = array => {
let set = new Set();
return array.map((v, index) => {
if(set.has(v.id)) {
return false
} else {
set.add(v.id);
return index;
}
}).filter(e=>e).map(e=>array[e]);
}
Any advice is appreciated, I am trying to make this with a very efficient Big-O. Cheers!
EDIT: So many awesome responses. Right now when I run the script with arbitrary object properties (similar to the answers) and I get an empty array. I am still trying to wrap my head around filtering everything out but on for objects that are referenced in memory. I am not positive how JS handles objects with the same exact key/values. Thanks again!
Simple Set will do the trick
let a = {'a':1}
let b = {'a': 1,'b': 2, }
let c = {'a':1}
let arr = [a,b,c,a,a,b,b,c];
function filterSameMemoryObject(input){
return new Set([...input])
}
console.log(...filterSameMemoryObject(arr))
I don't think you need so much of code as you're just comparing memory references you can use === --> equality and sameness .
let a = {'a':1}
console.log(a === a ) // return true for same reference
console.log( {} === {}) // return false for not same reference
I don't see a good reason to do this map-filter-map combination. You can use only filter right away:
const unique = array => {
const set = new Set();
return array.filter(v => {
if (set.has(v.id)) {
return false
} else {
set.add(v.id);
return true;
}
});
};
Also if your array contains the objects that you want to compare by reference, not by their .id, you don't even need to the filtering yourself. You could just write:
const unique = array => Array.from(new Set(array));
The idea of using a Set is nice, but a Map will work even better as then you can do it all in the constructor callback:
const unique = array => [...new Map(array.map(v => [v.id, v])).values()]
// Demo:
var data = [
{ id: 1, name: "obj1" },
{ id: 3, name: "obj3" },
{ id: 1, name: "obj1" }, // dupe
{ id: 2, name: "obj2" },
{ id: 3, name: "obj3" }, // another dupe
];
console.log(unique(data));
Addendum
You speak of items that reference the same object in memory. Such a thing does not happen when your array is initialised as a plain literal, but if you assign the same object to several array entries, then you get duplicate references, like so:
const obj = { id: 1, name: "" };
const data = [obj, obj];
This is not the same thing as:
const data = [{ id: 1, name: "" }, { id: 1, name: "" }];
In the second version you have two different references in your array.
I have assumed that you want to "catch" such duplicates as well. If you only consider duplicate what is presented in the first version (shared references), then this was asked before.