Reputation: 139
I have following reducer code in my react-redux app:
case 'TOGGLE_CLIENT_SELECTION':
const id = action.payload.id;
let newState = Object.assign({}, state);
newState.list.forEach((client) => {
if (client.id == id) client.selected = !client.selected;
});
console.log(state.list[0].selected + ' - ' + newState.list[0].selected)
return newState;
If I got it right - Object.assign creates brand new object, but console.log displays "true - true" of "false - false". Any thoughts why it behaves like this and how can I avoid this behavior?
Upvotes: 9
Views: 7643
Reputation: 25835
Object.assign({}, ...)
creates a shallow cloneObject.assign({}, state)
will copy properties from state
. String, number and boolean properties are copied "by value", while object properties (including Dates) are "by reference", which means the new properties point to the same objects as the old properties.
So, for example, given:
const otherState = { count: 10 };
const state = {
text: "foo",
otherState: otherState,
};
const newState = Object.assign({}, state);
newState.text = "bar";
newState.otherState.count = 9;
newState.text
will have a brand new string assigned to it, and state will not be affected. But both state
and newState
refer to the same instance of otherState
, so when you updated count
to 9, both state.otherState.count
and newState.otherState.count
are affected:
state = {
text = "foo"
otherState ──────┐
} │
│
│
├────> { count = 9 }
│
newState = { │
text = "bar" │
otherState ──────┘
}
When using Object.assign
, use the rule of three: if you get to the third property, you are now working in "shared" state:
newState.otherState.count = 0;
// ^ ^ ^
// ╵ ╵ ╵
// 1 2 3
A quick and easy work around, as suggested by Tim, is to use JSON.stringify:
let newState = JSON.parse(JSON.stringify(state));
But this is not foolproof. It'll work for some simple scenarios, but there are many scenarios that might fail. For example, circular references break JSON.stringify:
let x = { id: "x" },
y = { id: "y" };
x.y = y;
y.x = x;
// fails with: "Uncaught TypeError: Converting circular structure to JSON"
let cloneX = JSON.parse(JSON.stringify(x));
If you know the structure that you're trying to clone, then you can use your own logic to handle the copying and not fall into endless loops:
function cloneX( x ){
const c_x = { id: x.id };
const c_y = { id: x.y.id };
c_x.y = c_y;
c_y.x = c_x;
return c_x;
}
let x = { id: "x" },
y = { id: "y" };
x.y = y;
y.x = x;
let x2 = cloneX(x);
x2.y.id = "y2";
console.log( `x2.y.id was updated: ${x2.y.id}` );
console.log( `x.y.id is unchanged: ${x.y.id}` );
It's also conceivable that, with some creative use of WeakMap, you could come up with some logic that handles unknown data structures by tracking recursion and allowing deep copies.
It'd probably be easier to just use a library, though.
Upvotes: 12
Reputation: 1
You can use the following to make a deep copy and then use Object.asign
with it:
const x = { id: "x" },
y = { id: "y" };
x.y = y;
y.x = x;
const cloneX = Object.fromEntries(Object.entries(x));
const cloneChangeX = Object.assign(cloneX, {z: {id: "z"}});
console.log('const x = ', x, '\nconst cloneX = ', cloneX, '\nconst cloneChangeX = ', cloneChangeX);
Finally, you can do:
const cloneChangeX = Object.assign(
Object.fromEntries(Object.entries(x)),
{ changekey: changevalue }
);
Upvotes: 0
Reputation: 19788
According to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign, Object.assign
modifies the target
object rather than creating a new one.
Upvotes: 1
Reputation: 6701
Try this : let newState = state.map(item => Object.assign({}, ...item))
This will create a new object without any reference to the old object state
Upvotes: 2
Reputation: 4888
True, but it's not a deep copy.
The new object contains a reference to the old list.
Here's a trick to get around it (there's more "proper" ways some might say):
JSON.stringify the original. Then JSON.parse that string. The new object will be a "deep" copy (not sure if that's really technically "deep copying"). It works fine unless your sub-types are something more complex than standard plain old JSON-acceptable stuff.
Upvotes: 8