Simplify, Groupify, Conquer: The convenience of Object.GroupBy() in JavaScript
Tags: javascript, webdev, 100daysofcode, beginners
Published: 2023-12-17 17:22:34

It was a regular day of coding when I decided to revisit an old project of mine. As I scrolled through my code, I noticed a piece of code I had written during the earlier days of my JavaScript journey. The project was an Expense Tracker web application that allowed users to manually enter transactions and store them in LocalStorage. The specific code snippet I encountered was related to the grouping of data.

Back then, I remember grappling with the challenges of grouping data and attempting solutions using `filter()`, `map()`, and even `forEach()`. However, to my surprise, I discovered that there was an actual method designed for conveniently grouping data.

A sample from the actual set of data to be handled is as follows: 

```
const transacHistory = [
    { type: 'Income', desc: 'Salary', amount: '52000', date: '09/01/2023' },
    { type: 'Expense', desc: 'House rent', amount: '7500', date: '09/03/2023'},
    { type: 'Expense', desc: 'Shopping', amount: '8000', date: '09/11/2023' },
    { type: 'Income', desc: 'YouTube', amount: '3508', date: '09/12/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '550', date: '9/02/2023' },
    { type: 'Expense', desc: 'Internet', amount: '2250', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '350', date: '9/18/2023' },
]
```
Now, I wanted to group them based on the type of transaction made ( expense or income )
## The old-school data grouping technique

```
const groupedTransacHistory = transacHistory.reduce((acc, item)=>{
    if(!Object.keys(acc).includes(item.type)){
        acc[item.type] = [item]
    }
    else{
        acc[item.type].push(item)
    }
    return acc;
}, [])
console.log(groupedTransacHistory)

{*
OUTPUT:
[
  Income: [
    { type: 'Income', desc: 'Salary', amount: '52000', date: '09/01/2023' },
    { type: 'Income', desc: 'YouTube', amount: '3508', date: '09/12/2023' }
  ],
  Expense: [
    { type: 'Expense', desc: 'House rent', amount: '7500', date: '09/03/2023' },
    { type: 'Expense', desc: 'Shopping', amount: '8000', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '550', date: '9/02/2023' },
    { type: 'Expense', desc: 'Internet', amount: '2250', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '350', date: '9/18/2023' }
  ]
]
*}

```
The code to achieve the above output might seem to be a bit complex, but anyone familiar with the `filter()` method might understand the logic. If you wish to know more about JavaScript's `filter()` method, refer to the official MDN docs embedded at the bottom of this article.  The same logic can also be implemented using `map()` and `forEach()` methods as well.  As their logic is similar, I've included only the code associated with the `filter()` method.

## Know more about the `groupBy()` method:
Before letting you know the actual solution using the proposed `groupBy()` method, let's analyse how to use that in the first place.
The `Object.groupBy()` method is used to categorize individual items of an iterable.  The categorization is done according to the coerced string values returned by the provided callback function.  The grouped/categorized object thus returned contains unique keys with each key corresponding to an array of related values.  Is it sound confusing? You are on the right path. Follow along to see this method in action.

**SYNTAX**

![Syntax of Object.groupBy() method in JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uz59zt9beqbi5ouphhuo.PNG)
where, 
- The `iterable `is the element to be grouped

- The `callbackFunction `is the function to be passed to execute each element in the iterable.  This function should return a value which is then coerced into a string.
`function callbackFunction(element, index){...}`
- The `callbackFunction `accepts `element `and `index `as its arguments
where,

- The `element` represents the current element which is being passed to the `callbackFunction` at the moment and the `index` is the index of the `element` itself.
Let's look at some general and simple examples to grasp the concept.

## Examples
The following code groups items based on whether they start with a vowel or a consonant.

<u>1. The traditional approach:</u>
 

```
const vowels = ['a', 'e', 'i', 'o', 'u']
const cartItems = ['apples', 'bananas', 'cherries', 'umbrellas', 'cat food', 'pedigree', 'cupcakes']
const res2 = cartItems.reduce((accumulator, item) => {
    if (vowels.includes(item.slice(0, 1))) {
        if (!accumulator['vowels']) {
            accumulator['vowels'] = [];
        }
        accumulator['vowels'].push(item);
    } else {
        if (!accumulator['non-vowels']) {
            accumulator['non-vowels'] = [];
        }
        accumulator['non-vowels'].push(item);
    }
    return accumulator;
}, {});
{*
OUTPUT: 
{
  vowels: ['apples', 'umbrellas'],
  non-vowels: ['bananas', 'cherries', 'cat food', 'pedigree', 'cupcakes']
}
*}
```

<u>2. The `Object.groupBy()` approach:</u>

```
const vowels = ['a', 'e', 'i', 'o', 'u']
const cartItems = ['apples', 'bananas', 'cherries', 'umbrellas', 'cat food', 'pedigree', 'cupcakes']
Object.groupBy(cartItems, (item)=>vowels.includes(item.slice(0, 1)))
//item.slice(0,1) returns the first letter of the currently iterated item and checks whether it is a vowel.
{*
OUTPUT: 
{
  true: ['apples', 'umbrellas'],
  false: ['bananas', 'cherries', 'cat food', 'pedigree', 'cupcakes']
}
*}
```
If there are no object properties to be displayed in the case of arrays, the `Object.groupBy()` method simply uses the Boolean values as keys. If we need precisely set keys, then we should implement `Map.groupBy()` method which is the replica of our former method but lets us set keys instead of the default Boolean.
```
const fictionalCharacters = [
  { character: 'Ironman', ability: 'Fights bad guys' },
  { character: 'Barbie', ability: "Doesn't fight" },
  { character: 'Black widow', ability: 'Fights bad guys' },
  { character: 'Oggy', ability: "Doesn't fight" },
  { character: 'Peppa Pig', ability: "Doesn't fight" }
]
Object.groupBy( fictionalCharacters, (item)=>item.ability )
{* The value of item.ability is type-converted into a string and used as a key to group similar items in arrays, where the key's value is an array of corresponding items. Example: { item.ability: [{}, {}] } *}
{*
OUTPUT:
[
   Fights bad guys: [
      { character: 'Ironman', ability: 'Fights bad guys' },
      { character: 'Black widow', ability: 'Fights bad guys' },
   ],
   Doesn't fight: [
      { character: 'Barbie', ability: "Doesn't fight" },
      { character: 'Oggy', ability: "Doesn't fight" },
      { character: 'Peppa Pig', ability: "Doesn't fight" }
   ]
]
*}
```
So, as now we have a pretty decent understanding of how it works, let's get back to our expense tracker example.

```
const transacHistory = [
    { type: 'Income', desc: 'Salary', amount: '52000', date: '09/01/2023' },
    { type: 'Expense', desc: 'House rent', amount: '7500', date: '09/03/2023'},
    { type: 'Expense', desc: 'Shopping', amount: '8000', date: '09/11/2023' },
    { type: 'Income', desc: 'YouTube', amount: '3508', date: '09/12/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '550', date: '9/02/2023' },
    { type: 'Expense', desc: 'Internet', amount: '2250', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '350', date: '9/18/2023' },
]
Object.groupBy(transacHistory, (item)=>item.type)
{*
OUTPUT:
[
  Income: [
    { type: 'Income', desc: 'Salary', amount: '52000', date: '09/01/2023' },
    { type: 'Income', desc: 'YouTube', amount: '3508', date: '09/12/2023' }
  ],
  Expense: [
    { type: 'Expense', desc: 'House rent', amount: '7500', date: '09/03/2023' },
    { type: 'Expense', desc: 'Shopping', amount: '8000', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '550', date: '9/02/2023' },
    { type: 'Expense', desc: 'Internet', amount: '2250', date: '09/11/2023' },
    { type: 'Expense', desc: 'Veggies', amount: '350', date: '9/18/2023' }
  ]
]
*}
```
Simple as that. This is how the groupBy() method is convenient to use and understand. 

_NOTE:_

- Instead of making a huge leap to the static methods like Object.groupBy(), beginners should be comfortable using map(), filter() and reduce() methods.

- The Object.groupBy() method, as of 17th Dec 2023, is not supported in all browsers and it is expected to be in the near future.

![Browser compatibility of Object.groupBy() method](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkfkwng6pp829zxugort.PNG)
 
Refer to the following document to learn more about the filter() method in JavaScript:
{% embed https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter %}
To know more about Object.groupBy() method, refer to the following official docs by MDN
{% embed https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy %}

Follow me along my web development journey on [twitter/X](https://twitter.com/Subaashbala)