Table of Contents

  1. Shallow Copy
  2. Deep Copy
  3. Problem
  4. Solution
  5. Wrap Up

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.