clubmate.fi

A good[ish] website

Web development blog, loads of UI and JavaScript topics

The unexpected mutability when editing nested arrays

Filed under: JavaScript— Tagged with: object, iteration

I faced a completely thick problem when I was trying to filter a nested array object. I kept mutating the original variable, even though .map() or .filter() do not mutate the array they’re operating on.

More specifically I was generating tables from a array, and had a moment where I was sure I had discovered a bug JavaScript. That’s of course never the case, JavaScript worked as it should.

Unwanted mutation

In the following code I tried to filter out the objects with green apples:

const tableData = [
  {
    title: 'Apples',
    rows: [
      { name: 'Pink Lady', color: 'light red' },
      { name: 'Granny Smith', color: 'green' },
      { name: 'Red Delicious', color: 'red' }
    ]
  }
  // More fruit tables here...
]

// First map the tables
const filteredTable = tableData.map(data => {
  // Filter the rows
  const filteredColumns = data.rows.filter(row => {
    return row.color.includes('red')
  })

  // Put the filtered data back into the shape
  data.rows = filteredColumns
  return data
})

// The original data will be mutated like the filtered data, this condition
// should evaluate `true`
const assert = JSON.stringify(tableData) === JSON.stringify(filteredTable)

window.alert(assert)

At a glance this should work, since tableData is stored into a new variable as it’s being mapped and filtered. But, tableData is 'magically' mutated to have the same shape as the filtered data.

Achieving immutability

The unwanted mutation happens because the object inside the array is edited. New object needs to be created inside the filter:

const filteredTable = tableData.map(data => {
  const filteredColumns = data.rows.filter(row => {
    return row.name.includes('red')
  })

  return {    title: data.title,    rows: filteredColumns  }})

Or spread the data, if that’s more convenient:

return {
  ...data,
  rows: filteredColumns
}

Or use Object.assign(), whatever is the best for your use-case:

return Object.assign({}, data, {
  rows: filteredColumns
})

Conclusions

I was contemplating if this is blog-post-worthy at all, but hope this comes helpful to someone searching.

Thanks for reading!

Comments would go here, but the commenting system isn’t ready yet, sorry. Tweet me @intterne if you want to make a correction etc.

  • © 2022 Antti Hiljá
  • About
  • Follow my new Twitter account → @intterne
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • I love u!