Table of Contents
Shallow Copy
Shallow Copy is a copy whose properties share the same references (point to the same underlying values) as those of the source object from which the copy was made. As a result, when you change either the source or the copy, you may also cause the other object to change too — and so, you may end up unintentionally causing changes to the source or copy that you don’t expect.
const obj = {
name: 'Tin',
age: 22
}
const clonedObject = obj
clonedObject.age = 18
console.log(obj.age) // 18
console.log(clonedObject.age) // 18
Deep Copy
Deep copy of an object is a copy whose properties do not share the same references (point to the same underlying values) as those of the source object from which the copy was made. As a result, when you change either the source or the copy, you can be assured you’re not causing the other object to change too; that is, you won’t unintentionally be causing changes to the source or copy that you don’t expect.
⚒️ Ways to deep copy an object:
1️⃣ Using Spread operator
const obj = {
name: 'Tin',
age: 22
}
const clonedObject = {...obj}
clonedObject.age = 18
console.log(obj.age) // 22
console.log(clonedObject.age) // 18
2️⃣ Using Object.assign()
const obj = {
name: 'Tin',
age: 22
}
const clonedObject = Object.assign({}, obj)
clonedObject.age = 18
console.log(obj.age) // 22
console.log(clonedObject.age) // 18
🆘 The Risk
There is a problem that is nested object
const obj = {
name: 'Tin',
age: 22,
job: {
title: 'Software Engineer',
yoe: 2
}
}
const clonedObj = {...obj}
clonedObj.job.yoe = 5
console.log(obj.job.yoe) // 5
console.log(clonedObj.job.yoe) // 5
It is not full deep copy.
💡 Solution
To solve the problem, we stringify the object and then parse it.
const obj = {
name: 'Tin',
age: 22,
job: {
title: 'Software Engineer',
yoe: 2
}
}
const clonedObj = JSON.parse(JSON.stringify(obj))
clonedObj.job.yoe = 5
console.log(obj.job.yoe) // 23
console.log(clonedObj.job.yoe) // 5
Problem
When you have a nested data in an array or an object, the spread operator and Object.assign() will only DEEP-COPY the outer properties of the object. From the second child, it will only copy shallow.
✍️ Example
Assume we have a state
{
name: 'Tin',
age: 22,
job: {
title: 'Software Engineer',
yoe: 2
}
}
Some ways that developers are using to update the state
setState(prev => {
prev.job.yoe = 3
})
setState(prev => {
prev.job.yoe = 4
return prev
})
// These ways are not work because it doesn't return a deep object.
setState(prev => {
const cloned = {...prev}
cloned.job = {
title: 'Data Science'
}
return cloned;
})
// For example, assume we have Pure Component are watching prop {age}.
// But Pure Component will not regconize prop changes so it will not be re-rendered, because Pure Component will only compare shallow.
Solution
A solution for the problem is using hook useImmer. In essence, the hook will help us to create a deep copy of the state.
const [engineer, setEngineer] = useImmer({
name: 'Tin',
age: 22,
job: {
title: 'Software Engineer',
yoe: 2
}
})
// Update state
setEngineer(prev => {
prev.age = 23
return prev
})
Wrap Up
- Shouldn’t update states by shallow copy
- Use useImmer instead of useState to update nested object states.