FacundoGFlores
FacundoGFlores

Reputation: 8118

Avoiding object mutation

I am working with React and ES6. So I arrivied to the following case: I have an state with an array of objects suppose a = [{id: 1, value: 1}, {id: 2, value: 2}] in the state of Object A, then I pass the list to Object B by props, Object B (in the constructor) copy the list to its own state and call a function which is using map function where I return b = [{id: 1, value: 1, text: 'foo'}, {id: 2, value: 2, text: 'foo'}] (added (text, value) to each object), so it though it was not mutating a in Object A but it was.

So I made some tests:

const a = [{id: 1, value: 1}, {id: 2, value: 2}] // suppose it is in object A

addText = (list) => {
    return list.map((item) => {item.text = "foo"; return item})
}

const b = addText(a) // suppose it is in object B

so under my assumption a !== b, but a was mutated by addText, so they were equal.

In a large scale project programmers make mistakes (I did here!) how it is supposed to be handled this kind of situations to avoid mutating objects in this way? (The example tries to represet a as an state for Object A which is a component from React)

Upvotes: 6

Views: 11010

Answers (2)

Advait Junnarkar
Advait Junnarkar

Reputation: 3705

By directly referring to item.text you are changing it.

Map iterates over an array. Since your array has objects, unlike primitive values, it doesn't duplicate them in the function scope. Instead, it passes by reference.

The solution is to return new (duplicated) object values whilst iterating in the map function.

const a = [{id: 1, value: 1}, {id: 2, value: 2}]

addText = (list) => {
    return list.map((item) => {
        return {
            ...item,
            text: 'foo',
        };
    })
}

const b = addText(a)

You can simplify the syntax, and reduce indentation, by omitting the return statements as you are using arrow functions anyway.

const a = [{id: 1, value: 1}, {id: 2, value: 2}]

addText = list => list.map(item => ({
    ...item,
    text: 'foo',
}));

const b = addText(a)

Upvotes: 0

Andy_D
Andy_D

Reputation: 4228

If your change to the object is really that shallow (at the top level of the object), you can use Object.assign({}, oldObj, newObj), or if you have the Object spread proposal enabled in babel, { ...oldObj, newThing: 'thing' }

To enforce this on a team, you could use this ESLint plugin https://github.com/jhusain/eslint-plugin-immutable with the no-mutation rule enabled.

Upvotes: 5

Related Questions