Pass by reference issue I encountered In JavaScript

This post is a note of how I tackled a simple issue of mutating object in an array when I tried to update the state. If you know arrays are passed by reference in Javascript then the issue shouldn’t bother you at all :)

We all know that keep data immutable is one of the best practices of programming, that’s why we have immutable.js, lodash and underscore for Javascript. Today I encountered a simple related issue when I tried to keep my state of the react component unchanged. It took me a bit while to figure it out but it turned out the reason why it happened is so obvious, I note it here maybe someone will encounter the similar issue and find it helpful some other days.

So here is the thing

The case is that I have an array of data of the documentation information stored in the state, which look like this:

data = [
{
id: 13,
title: 'N100 user guide',
document_url: 'document/n100_user_guide.pdf'
lang: 'en'
},
{
id: 14,
title: 'Y11 brochure',
document_url: 'document/y11_brochure.pdf'
lang: 'en'
},
// ...
]

In the same page, I had a few form inputs which could edit each title of the data, and the inputs were attached the same handler which would update the data in the state whenever the input was changed.

The data flow was like this:

  1. This component received data from props.
  2. Call setState({data}) to store the data in the state.
  3. Render the data into the form inputs
  4. Users change the input value.
  5. The input triggers the handler which will update the state.
  6. Component re-render the input.
( Not sure it is the best practice, any suggestion is appreciated :) )

About the issue

The issue I encountered was in step 5 when the handler needed to update the title value of specific data in the array.

handlerNameChange(e){
  // Get the index of the data which I stored in the input dataset.
const dataIndex = e.target.dataset.index;
const newTitle = e.target.value;
  // Stop the function if the title isn't changed.
if (this.state.data[dataIndex].title === newTitle) return;

// Copy array to in order not to mutate the state.
const updatedData = [...this.state.data];

// Change the value of the desired title inside the copied array.
updateData[dataIndex].title = newTitle;
  // Update the state, everything seems so easy peasy.
this.setState({ data: updatedData });
}

Everything looks so natural, copying the array and only mutating the data inside the array.

Unfortunately, it turned out that the state was mutated when I call updateData[dataIndex].title = newTitle .

I found the issue because the data was copied from the props, and whenever I inputted a new value and the props.data was changed at the same time as well.

Why!?

Array is just a list of references

So the thing is that I forgot that the array is just a list of references (or pointers ?), so even if you copy the array, the javascript engine just create another list of references points to the same values.

const a = [1, 2, 3]
const b = [...a]
// Copy the same references and store in b.
// Now each reference in these array point at the same values.
console.log(a)         // [1, 2, 3]
console.log(b) // [1, 2, 3]
console.log(a === b) // false
// They have same references but they are different.

When we store the objects in an array, we just simply save the references in the array, not the objects themselves.

If there is an array copied from another array which stores a bunch of objects, when you mutate one object stored in the first array, the object stored in the second array is change as well. Because they are actually same objects.

const A = [{ name: 'TOM' }, { name: 'BOB' }, { name: 'COCO' }];
const B = [...A];
// Now A and B point at the same objects
A[0].name = 'ALICE';
console.log(A[0].name) // 'ALICE'
console.log(B[0].name) // 'ALICE', not 'TOM' because the object is mutated

That’s pretty much of it, it just that simple concept.

Back to the handler

So even if I copy the data from the state, I still mutate the object stored in the array which causes the state and props mutated.

handlerNameChange(e){
const dataIndex = e.target.dataset.index;
const newTitle = e.target.value;
if (this.state.data[dataIndex].title === newTitle) return;
const updatedData = [...this.state.data];
// MUTATE!
updateData[dataIndex].title = newTitle;
this.setState({ data: updatedData });
}

My solution is simple:

  1. Copy the array of data.
  2. Copy the data which is about the update.
  3. Update the copied data.
  4. Update the copied array at the certain index to point at the copied data.
  5. Update state.
handlerNameChange(e){
const dataIndex = e.target.dataset.index;
const newTitle = e.target.value;
if (this.state.data[dataIndex].title === newTitle) return;

// 1. Copy the array of data.
const updatedData = [...this.state.data];
  // 2. Copy the data which is about the update.
// 3. Update the copied data.
const dataToUpdate = {
...this.state.data[dataIndex],
title: newTitle
};

// 4. Update the copied array at certain index to point at the copied data.
updatedData[dataIndex] = dataToUpdate;
  // 5. Update state   
this.setState({ data: updatedData });
}

With this method, everything is not mutated everyone is happy :))


Hope you enjoy the post and find something useful. If there is anything you would like to say or share welcome to leave some comments below. Any response is welcome and clap is highly appreciated which will make my day, thank you :)

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.