Get super confident at JavaScript. Javascript Icon

I promise to change your JavaScript skills!

Create a Group By Function with Array.prototype.reduce

Learn how to write a custom group by function in JavaScript by using Array.prototype.reduce.

After learning about the upcoming Array.prototype.group feature in JavaScript, I couldn’t help but want to create a streamlined version to use now.

Here’s what I came up with, using Array.prototype.reduce as the base, to achieve the same result in a minimalistic way.

Read my article on Grouping Arrays of Objects in JavaScript with the Array.prototype.group method.

Let’s take the following data structure, take note of the type property:

const data = [
  { type: 'food', name: 'Pizza' },
  { type: 'drink', name: 'Coffee' },
  { type: 'food', name: 'Hot Dog' }
];

With the Array.prototype.group method, it gives us a new object with each type value as a key, followed by an array of the relevant elements:

const { food, drink } = data.group((item) => item.type);

// ✅ [{ type: 'food', name: 'Pizza' }, { type: 'food', name: 'Hot Dog' }]
console.log(food);
// ✅ [{ type: 'drink', name: 'Coffee' }]
console.log(drink); 

So how could we do it? There are of course multiple ways, it might be that we could use something like Array.prototype.filter and then compose an object from the results of each call.

However, that would require multiple loops which would be far less optimized to handling larger data structures.

First, let’s start with our function definition to emulate the native prototype method:

const group = (items, fn) => {};

group(data, (item) => item.type);

That’s our API footprint out of the way, what next?

const group = (items, fn) => {
  return items.reduce((prev, next) => {
    const prop = fn(next);
  }, {});
};

I’ve added a callback fn argument, which takes the first value of our array and passes it to our callback to get the property, the next value.

From here, this will be type which we’ll use to look up the items in the array.

Now for the crazy part, composing the object. You’ll notice the starting value of our reduce is actually an object {} on the last line. This means we’ll start with this object and slowly build it to contain our new values, using the chosen type property.

First we’ll merge in any existing values, which allows us to keep reusing our object, and secondly add a dynamic property setter from our prop to set the new value:

const group = (items, fn) => {
  return items.reduce((prev, next) => {
    const prop = fn(next);
    return {
      ...prev,
      [prop]: undefined
    };
  }, {});
};

This gives us { food: undefined, drink: undefined }, so now we need to fill in the goodness.

Next we need to create an array result for each property, so let’s start with that and ‘append’ the current item in the loop to it:

const group = (items, fn) => {
  return items.reduce((prev, next) => {
    const prop = fn(next);
    return {
      ...prev,
      [prop]: [next],
    };
  }, {});
};

But at this point, on every iteration of the loop, the result would be lost because we are setting the value each time.

We need to merge any existing values into an array value, or set a new one:

const group = (items, fn) => {
  return items.reduce((prev, next) => {
    const prop = fn(next);
    return {
      ...prev,
      [prop]: prev[prop] ? [...prev[prop], next] : [next],
    };
  }, {});
};

If the prev[prop] exists, set the array with all the current values, and the next value. Otherwise, set the array to just the next value to initialize it.

And that’s it! We could then clean things up with some implicit return statements and done:

const group = (items, fn) => items.reduce((prev, next) => {
  const prop = fn(next);
  return {
    ...prev,
    [prop]: prev[prop] ? [...prev[prop], next] : [next],
  };
}, {});

This won’t be as performant as Array.prototype.group, but that’s not available in browsers just yet without a polyfill.

🏆 P.S. check out my latest JavaScript courses if you’re serious about taking your skills to the top.

Thanks for reading, happy grouping!

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover