I have this code that I am testing on jsfiddle
onVote = (dir, index) => {
console.log(this.state)
const products = [...this.state.products]
products[index].votes = dir ? products[index].votes + 1 : products[index].votes - 1
console.log(this.state.products[index].votes)
// this.setState({products})
};
https://jsfiddle.net/hkL3wug7/2/
However, even though I am not setting State, the console log shows that the state is changes every time I click on the plus and minus signs.
I did the same as in this article https://medium.com/#giltayar/immutably-setting-a-value-in-a-js-array-or-how-an-array-is-also-an-object-55337f4d6702
const newState = [...state] // clone the array
newState[action.index].done = true
return newState
as far as I understand
(it is not duplicate of the other question, I am not asking for an efficient way)
As #Carcigenicate mentioned, you have created a shallow copy of the array which means you have a new array pointing to the same objects in the original.
To avoid mutating the original object, you will need to also create a copy of the one you would like to mutate, e.g.:
// Shallow copy of the array
const products = [...this.state.products];
// Shallow copy of the object within the array
const updatedProduct = { ...products[index] };
// Update the copy of the object
updatedProduct.votes = dir ? updatedProduct.votes + 1 : updatedProduct.votes - 1;
// Replace the object with the updated copy
products[index] = updatedProduct;
As #Carcigenicate mentioned in the comment, using the spread operator creates a shallow copy of the array. This is creating a problem for you because the expanded version of the array contains Objects which are passed by reference. So even though your local variable products is a new copy of this.state.products, they both contain references to the same Objects.
To achieve what you are trying to do, you would have to clone the objects in this.state.products. One possible way to do this is using Object.assign and replace your const products = [...this.state.products] with:
const products = [
Object.assign({}, this.state.products.Orange),
Object.assign({}, this.state.products.Apples),
Object.assign({}, this.state.products.Bananas)
]
Related
I have been trying to filter an Array by its props and not by its value so my original array would be -
const orignalArray =[
{id: 1, name:"jim", email:"jim#mail.com",age:20},
{id: 1, name:"jom", email:"jom#mail.com",age:30}
]
id like to be able to use (n) amount of filters.
My output array would look ideally look like this
const filterList["id","age"]
const newArray=[{name:"jim", email:"jim#mail.com"},{ name:"jom", email:"jom#mail.com"}]
I have tried to use filter() but cant seem to get it to work.
any help is much appreciated.
In this case you aren't filtering the array rather creating a new one based on the original with derived elements from each. To achieve this you can use the array map function to loop over the array and create a new one with new objects without the desired properties, e.g.:
function removeArrayElementProps(arr, propsToRemove) {
return arr.map((element) => {
// Create a copy of the original element to avoid modifying the original
const newElement = {...element};
// Remove the desired properties from the new element
propsToRemove.forEach((propToRemove) => {
delete newElement[propToRemove];
});
// Returning the modified element in map puts it in thew new arry
return newElement;
});
}
Then you simply call:
const newArr = removeArrayElementProps(orignalArray, ['id','age']);
This loops over the array, creates a copy of each element, and removes the specified properties from the element.
This is mutating my redux state somehow. I don't know why:
allProducts is a state in my store with initial_state of [] and gets values from api call.
const [products, setProducts] = useState([])
const prevProducts = [...allProducts];
setProducts(prevProducts);
const updateProductPrice = (newPrice, index) => {
const newProducts = [...products];
newProducts[index].price = newPrice;
setProducts(newProducts);
console.log(allProducts);
}
When I console.log(allProducts), it's showing me an updated array that has the values of newProducts
I think what you are seeing has to do specifically with how JavaScript is storing object references inside your arrays. When you copy an array, you do not do a full re-creation of all the objects in it, even when using the spread operator, you are only copying the references to the objects at each index.
In other words, assuming they share matching indexes, each item at newProducts[i] === allProducts[i] - i.e. is the exact same object instance.
See, for example, https://javascript.info/object-copy and as well there are many references for "shallow" and "deep" copying for objects.
The objects are the same in memory even though I changed the reference.
"A variable assigned to an object stores not the object itself, but its “address in memory” – in other words “a reference” to it."
I used _.cloneDeep from https://lodash.com/docs/4.17.15#cloneDeep
So I changed my code to:
const prevProducts = _.cloneDeep(allProducts);
setProducts(prevProducts);
Another solution: In case you do not want to use lodash.clonedeep package, you can do the same using below:
const array = [{a: 1}]//initial array
const copy = array.map(item => ({...item}))
interface list {
name: string,
id: number,
marks: number
}
component state :
{
listOfStudents: list[],
}
now on some event, where marks is modified for a student with given name and following handler function, the following doesn't work
currentList = this.state.listOfStudents;
//find the list object with the given name
listToModify = currentList.map(item, ....=> item.name === event.name);
// update its marks
listToModify.marks = event.marks;
//set new state
this.setState({listOfStudents: currentList});
But the following works:
//get a copy of the object
currentList = [...this.state.listOfStudents];
//find the list object with the given name
listToModify = currentList.map(item, ....=> item.name === event.name);
// update its marks
listToModify.marks = event.marks;
//set new state
this.setState({listOfStudents: currentList});
I didn't have to add new data but modify an existing one, why is mutation required in such a case?
for a given state that is an object or array, react uses only its reference to compare and decide if it will update state, it doesn't do deep comparision between objects values.
the first case you are passing the same reference, therefore there is no update state. actually, if you modify the array at first example, that's actually mutating state directly.
second case you are cloning into a new fresh array, which holds a different reference. since the reference is different from previous array, react now update its state. that's actually the right approach to update state when dealing with arrays/objects.
I want to re-render my UI base on the value of array a as follows:
const [a, setA] = useState([<sample_data>];
...
function update(newValue) {
// use push method to append the new element into a
a.push(newValue);
setA(a);
// use concat to create new array that includes a & new element
setA([].concat(a,[newValue]);
}
If I use push, the re-render doesn't trigger, but using concat can.
It's weird to me, the array a really changes in both of cases, why only the concat trigger the re-rendering?
setA checks using oldValue === newValue
function setA(newValue) {
const same = oldValue === newValue;
if (!same) {
update(...);
}
}
In the push case you're passing the same array
a.push(..)
setA(a);
same = a === a // true
Concat creates a new array which you pass in
setA(a.concat(...))
same = a === newArray // false
note you can use the spread operator
setA([...a, newValue]);
It's apparently much faster than concat
Both work differently.
concat()
Array.prototype.concat() returns a new array with concatenated element without even touching in original array. It's a shallow copy.
push()
Array.prototype.push() adds an element into original array and returns an integer which is its new array length.
If you are using ES6, you can use the spread operator
const [a, setA] = useState([<sample_data>];
...
function update(newValue) {
setA([].concat(a,[newValue]);
or
setA([...a, newValue])
}
So I'm using ngrx for managing the state in my application. I tried to add a new property (selected shifts) which should look like this:
state: {
shifts: {
selectedShifts: [
[employeeId]: [
[shiftId]: shift
]
]
}
}
at the moment, my state looks like this:
state: {
selectedShifts: {
[employeeId]: {
[shiftId]: shift
}
}
}
so as you can see, my "selected shift" is a property, not an array - which makes it diffictult to add/remove/query the state.
How do I compose the state to look like I want it?
This is what I tried in the reducer:
return {
...state,
selectedShifts: {
...state.selectedShifts,
[action.payload.employeeId]: {
...state.selectedShifts[action.payload.employeeId],
[action.payload.shiftId]: action.payload[shift.shiftId]
}
}
};
Now when I try to return the state in the way I'd like to, this is the result:
state: {
selectedShifts: {
[action.payload.employeeId]:
[0]: {[action.payload.shiftId]: { shift }}
}
}
What am I missing here? When I try to replace the {} items which should be [] this error comes up: "," expected.
Oh yea, I would like the index of the array to be the id of the specific shift and not [0], [1]...
Is this possible at all?
Would it be a bad idea to change the index from numerics to the actual shift's id?
Array length kind of miss behaves when you add data at numeric index points. This might get you into problems with array methods using length join, slice, indexOf etc. & array methods altering length push, splice, etc.
var fruits = [];
fruits.push('banana', 'apple', 'peach');
console.log(fruits.length); // 3
When setting a property on a JavaScript array when the property is a valid array index and that index is outside the current bounds of the array, the engine will update the array's length property accordingly:
fruits[5] = 'mango';
console.log(fruits[5]); // 'mango'
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 6
There is no problem selecting / updating state from object, it's just a bit different from what you're probably used to. With straight hashmap { objectId: Object } finding the required object to update / remove is the fastest possible if changes are defined for object id.
I know your problem is related to NGRX but reading Redux immutable patterns is going to definitely help you out here for add / update / remove objects from the state. https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns
Generally you don't want to have arrays in state ( at least large arrays ) object hashmaps are a lot better.
To get array of your selected user shifts for views you could do something like. Note this is not a shift indexed array just array of shifts under userId property. From original state form following state.
state: {
selectedShifts: {
[employeeId]: {
[shiftId]: shift
}
}
}
const getSelectedShiftsAsArray = this.store.select( getSelectedShifts() )
.map(
userShifts => {
// get array of object ids
const userIds = Object.keys( userShifts );
const ret = {};
for( const userId of userIds ) {
const collectedShifts = [];
// convert Dictionary<Shift> into a Shift[]
// get array of shift ids
const shiftIds = Object.keys( userShifts[userId] );
// map array of shift ids into shift object array
collectedShifts = shiftIds.map( shiftId => userShifts[shiftId] );
// return value for a userId
ret[userId] = collectedShifts;
}
return ret;
});
Code is completely untested and just for a reference one level up from pseudocode. You could easily convert that into a NGRX selector though. The state is there just for the storage, how you model it for use in components is upto selector functions & components themselves.
If you really really need it you could add.
ret[userId].shiftIds = shiftIds;
ret[userId].shifts = collectedShifts;
But it really depends on how you plan to use these.
From my personal experience I would separate shift entities from selectedShifts but how you organise your state is completely up to you.
state: {
shifts: {
// contains shift entities as object property map id: entity
entities: Dictionary<Shift>,
selectedShifts: [
[employeeId]: number[] // contains ids for shifts
]
}
}
Now updating / removing and adding a shift would just be setting updated data into path shifts.entities[entityId]
Also selectedShifts for employeeId would be about checking if id is already in there and appending it into an array if it wasn't. ( If these arrays are humongous I'd go with object hash here too for fast access. <employeeId>: {shiftId:shiftId} ).
Check also:
redux: state as array of objects vs object keyed by id