.reduce() for transforming data

A pretty common task that we need to do as JS developers is to take a set of data in the form of an array and transform it into something else. But as compared to map, which always returns an array, reduce can give us any type of value.

For example, say we were making an app for a restaurant and we have an array of items that consists of every item on the restaurant's dinner menu:

const menuItems = [
  { item: "Blue Cheese Salad", price: 8 },
  { item: "Spicy Chicken Rigatoni", price: 18 },
  { item: "Ponzu Glazed Salmon", price: 23 },
  { item: "Philly Cheese Steak", price: 13 },
  { item: "Baked Italian Chicken Sub", price: 12 },
  { item: "Pan Seared Ribeye", price: 31 },
];

And instead of getting any array element, modifying it or checking whether it exists, we wanted to get the total price of all of these items combined. How would be go about doing that. Considering all of the array methods we've considered up until this point, none of them gives us the functionality to be able to do that.

However, we can use the .reduce function, which is built into all JS arrays. The first thing that we need to know about reduce is that it iterates over all of our array elements.

So let's write this out, just like we have before:

menuItems.reduce();

So like with map or filter, we have to pass it a function to perform the operation we like, so we'll add that:

menuItems.reduce(() => {});

However unlike map or filter, .reduce needs one additional argument, which is the initial value of the operation we want to perform.

In this case, since we're adding up all of the prices of our menu items and expect a number as a result, we want our initial value to be a number, namely 0.

menuItems.reduce(() => {}, 0);

And what is different about reduce is the way that it iterates over each element. Unlike our other array methods, where we just got the array element itself, before we get each element in the parameters of our function, we have a special value called our accumulator.

So let's write these two parameters, the accumulator, and each element after it, which will be our menuItem:

menuItems.reduce((accumulator, menuItem) => {}, 0);

In our other array functions we always returned the element itself, but what is different about reduce and the function that it is passed is that it always returns the accumulator. So what's the purpose of the accumulator? What is need about the accumulator is that you can think of it as a kind of safe; it's something that you can store anything you like in. The accumulator is what gives reduce it's great flexibility and utility. And reduce is going to 'remember' the value of the accumulator every time thing function runs, for every single element in our array. Just know that for the accumulator to work and store new values, it has to be returned every time the function runs

So with the accumulator we can gather and add together all the prices from each of the menuItems. So this may be a stretch, but knowing what you do now about the accumulator, take a minute, and think of what we can write to the function body to be able to add up all the numbers.

Let's remember what we know. First of all, we always need to return the accumulator, so let's add that first:

menuItems.reduce((accumulator, menuItem) => {
  return accumulator;
}, 0);

And then we want to get the price of the menuItem we are iterating over, which is menuItem.price and put it on the accumulator. Now your first guess, might have been to set the menuItems price equal to the accumulator, but that won't work for one simple reason:

menuItems.reduce((accumulator, menuItem) => {
  return (accumulator = menuItem.price);
}, 0);

And that's because of one thing we haven't mentioned yet, which is that the accumulator, for the first time this function is run for the first array element, is set to the initial value that we provided as the second argument, 0. So for the first time this function runs, the accumulator will be 0 and then it will be set to menuItem.price, which for the first menu item is 8. Then the accumulator will be returned and then for the second time this function runs for the second array element, the accumulator will hold onto that value 8, but it will then be set to the next price, 18, etc. So the accumulator's value will keep being overwritten.

Instead, we need to add the current menuItem's price to the accumulator which will start at 0:

menuItems.reduce((accumulator, menuItem) => {
  return accumulator + menuItem.price;
}, 0);

And then finally, let's store the total in a variable named as such and console.log it:

const total = menuItems.reduce((accumulator, menuItem) => {
  return accumulator + menuItem.price;
}, 0);
console.log(total);

So if we run this, it will work, but to see exactly why is works, let's console.log both the accumulator and the menu item price for every iterate to see what's happening:

menuItems.reduce((accumulator, menuItem) => {
  console.log(`
    accumulator: ${accumulator},
    menu item price: ${menuItem.price}
  `);
  return accumulator + menuItem.price;
}, 0);

So for the very first iteration and array element looped over, the accumulator was 0, which was exactly as we said, it was set to the initial value that we pass in as 0. Then the first price was 8 and we add that to the accumulator and return in. Then on the second element, the accumulator amount is remembered as 8, and for the second element price, 18, that also was added to the accumulator and returned. And again 3 more times the price was collected onto the accumulator and we get our correct total of 105.

So this process may seem to be a strange way of iterating over arrays, but once you understand it, you realize that reduce is the most powerful of all of the array methods. We can use reduce to get whatever information that we want, not just number totals. It can be used to transform an array into any data type, yes, including arrays. And in the next video we're going to see how to do just that.