data types in JS can be divided into primitive data types (eg. string, number) and non-primitive data types (eg. object),
primitive data types can not be modified (eg. using String.prototype.replace will always return a new instance of a string) while non-primitive data types can - and the reference pointing to that data will be "updated" as well (that's a big simplification, but executing eg. x.y = 2 will not create a new instance of x - instead the x will be keept the same in every place it's referenced),
which means to detect a change in the new version of the state (in a most performant way) it is required to create a new instance of an Object, Array (and other non-primitive data type representations)
// changing reference in top level
const newState = { ...oldState }
// changing nested reference
const newState = {
...oldState,
x: {
...oldState.x,
y: 2 // new "y" value
}
}
// changing nested reference, another example
const newState = {
...oldState,
x: {
...oldState.x,
y: {
...oldState.x.y,
z: 'test' // new "z" value
}
}
}
you can read more how change is detected on a basic level here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#comparing_objects