what is the best way for changing store array elements in Redux?I have these 3 working solutions.which is the best one? I think first two solutions are mutating the state. Is it true? If you have better solution I will be appreciate it.
In this example I have one order list and I want to increment quantity of one order in list. I pass this order index as orderIndex in action.
first way:
let order = state[action.orderIndex];
order.quantity++;
return Object.assign([],order);
second way:
let order= Object.assign([],state);
order[action.orderIndex].quantity++;
return Object.assign([],order);
third way:
return Object.assign([],state.map(
(order,index) => (index==action.orderIndex) ? {...order,quantity:order.quantity+1} : order)
);
I have another solution but it returns an object and I don't know how to return an array in this solution. :
return {...state,[action.orderIndex]:{...state[action.orderIndex],quantity:state[action.orderIndex].quantity+1}};
If you're amending an item in the array, create a new item with the new values, then return a new array slicing the elements before and after the one you're amending and inserting this in the middle. With ES6 spread syntax you can do something like:
var newArray = [
...originalArray.slice(0, indexOfAmendedItem),
amendedItem,
...originalArray.slice(indexOfAmendedItem + 1)
];
Watch this:
https://egghead.io/lessons/javascript-redux-avoiding-array-mutations-with-concat-slice-and-spread
it explains it brilliantly.
The first modifies the original order which is a mutation. This violates the immutability of the Redux store.
The second makes a copy of the array, but the objects inside the array are still the original orders. They simply reference the same instances as the original array. Therefore when you go to modify one of them on the next line, you are mutating the Redux state.
The third way is an interesting one because even though you are not mutating the state, it can be simplified.
return state.map(order => (index==action.orderIndex) ? {...order, quantity: order.quantity+1} : order);
Because the map
function already returns a new array, there's no need to call Object.assign
.
The third version works because you're referencing all the orders you do not modify and creating a new order only for the one item you want to change. Since mutation is not allowed, you need to create a new object.
Now, the ES6 syntax can be confusing. You can make it work entirely without ES6 (except for Object.assign
but you could replace that with extend
from underscore. The important thing is that were not using the new syntax).
// copy the list so we can modify the copy without mutating the original
// it is important to realize that even though the list is brand new
// the items inside are still the original orders
var newOrders = state.slice();
// grab the old order we'd like to modify
// note that we could have grabbed it from state as well
// they are one and the same object
var oldOrder = newOrders[action.orderIndex];
// this takes a new object and copies all properties of oldOrder into it
// it then copies all properties of the final object into it
// the final object has only our new property
// we could have multiple properties there if we needed
var newOrder = Object.assign({}, oldOrder, { quantity: oldOrder.quantity + 1 });
// now we just replace the old order with the new one
// remember it's okay to modify the list since we copied it first
newOrders[action.orderIndex] = newOrder;
return newOrders;
Alternatively, you can just modify the copy directly as seen below:
var newOrders = state.slice();
var oldOrder = newOrders[action.orderIndex];
// make a shallow copy
var newOrder = Object.assign({}, oldOrder);
// we can now modify immediate properties of the copy
newOrder.quantity++;
newOrders[action.orderIndex] = newOrder;
return newOrders;
The difference between the two ways of doing things is that the version using map
(as well as more ES6 heavy versions) is a single expression. This makes it more functional. Instead of specifying steps to get to the result you specify what the result should look like. It's declarative vs imperative.
The declarative version is sometimes clearer. Then again, often it just isn't. It is often shorter, but can seem very alien. If you hail from the functional world, then it looks and feels completely normal. Aliens never think of themselves as alien.
I suggest that ease of understanding is more important than shorter code. Note that it's unlikely to be any faster. The map
version, for instance, calls your function for each order.
If you have several million orders (as we all hope we'll get), we end up checking each order to find out if it's the one we want to modify.
When using the imperative version, however, you make a copy of the array, an operation during which we don't check any of the orders. It's very fast and much faster than the map
version.
So why do we see people using map
then? Well, performance is a non-issue if you only have a few orders, and it looks fine for the functional purists out there.
There's also Mark's answer with the ES6 way of doing things. It makes a new array by copying the part before the item you want to replace, adds your new item, and then adds the part after the item you're replacing. This is fast. It's also quite ugly (in my opinion).
Sure, it's clear what it does if you think about it. It replaces the one item we need changed out. However, it doesn't read like a replacement. As a matter of fact, the only item not mentioned by index is the one we are replacing.
I would like to say that unless you're trying to prove that your entire app can just be a single enormic expression, it is perfectly okay to write imperative code. If it's easier to understand or write then there's no reason not to.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With