The problem with arrays as a reference type

Since we're working with a restaurant, let's say we devise a feature for them to keep track of new menu ideas, we have a few of them here already, these are the lunch menu ideas:

const lunchMenuIdeas = ["Harvest Salad", "Southern Fried Chicken"];

And now we want to put these in a more general list, of all of the menu ideas, for all of the meals, breakfast, lunch and dinner, so let's make a new array--all menu ideas. So naturally, we'll set lunchMenuIdeas as the initial value of this array:

const allMenuIdeas = lunchMenuIdeas;

And now we can start working with it. And as you saw in the first lesson, we can easily add new items to the end of any array with the push method. So let's add take allMenuIdeas and push on something tasty, let's say a Club Sandwich:

allMenuIdeas.push("Club Sandwich");

So now let's look at our list by logging it:

console.log(allMenuIdeas); // 3 [], [], []

As you would expect, we now have three potential menu items, but while we're at it, let's look back at lunch menu ideas:

// console.log(allMenuIdeas);
console.log(lunchMenuIdeas); // 3 [], [], []

So what's happening here? Based off of what you know about objects as arrays as a subtype of objects, take a minute and see if you can answer in your own words.

What's happening is that as ultimately an object, arrays are reference types. When we passed lunchMenuIdeas to allMenuIdeas, we didn't pass a copy of the array, we passed a reference to it. So when we used the push method, we mutated the original lunchMenuIdeas array in the process.

So one of two things needs to happen here, we either need to:

  1. Use an array method that produces a new array, or
  2. When we perform an update to an array, we need to do it immutably, meaning by making a copy of it

.concat() as a non-mutating array method

For the first approach, we could swap out push for a non mutating array method that does the same thing, add items to the end. And there is such a method, called concat, so instead of passing lunchMenuIdeas as a reference to allMenuIdeas, we could just say:

// const allMenuIdeas = lunchMenuIdeas;
const allMenuIdeas = lunchMenuIdeas.concat("Club Sandwich");
console.log(allMenuIdeas);
console.log(lunchMenuIdeas);

And now allMenuIdeas has all 3 items, but lunchMenuIdeas was not mutated since concat returned us a new array instead of referencing the old one.

Array spread operator to copy array elements

or we could leave the code as we had it before, and use the array spread operator to clone the previous array. To use it, we can't just spread into nothing, we need to spread it into an array:

const allMenuIdeas = [...lunchMenuIdeas];

And once we do that, we can use whatever methods we like, even if they mutate the array itself, just as long as we create a new one each time. So we can use .push once again:

const allMenuIdeas = [...lunchMenuIdeas];

allMenuIdeas.push("Club Sandwich");
console.log(allMenuIdeas);
console.log(lunchMenuIdeas);

But now lunchMenuIdeas is no longer mutated.

Such a small error like this may seem benign, but in applications where data is very important to users such as in a checkout process, where the correct price is expect, it's very important to make sure data is updated correctly, and a result, immutably. To give you a sense of this, try to imagine if our lunchMenuIdeas array that was incorrectly mutated represented a user's shopping cart in an ecommerce app and when they checkout their total included items that they weren't expecting or didn't want included in the purchase.

So it's very important to avoid these unexpected consequences of mutations, but fortunately we have a great help through the use of the array spread operator when working with arrays.