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
The
iterable
is the element to be groupedThe
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
acceptselement
andindex
as its arguments
where,The
element
represents the current element which is being passed to thecallbackFunction
at the moment and theindex
is the index of theelement
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.
1. The traditional approach:
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']
}
*}
2. The Object.groupBy()
approach:
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.
Refer to the following document to learn more about the filter() method in JavaScript:
To know more about Object.groupBy() method, refer to the following official docs by MDN
Follow me along my web development journey on twitter/X
Top comments (9)
Good article. BTW, how does it differ from the other methods like filter(), map() and reduce() in performance?
Hi, thanks for the feedback. It does perform well when compared to filter(), map() and reduce().
The vowel example stated above consumes 6 times more time than to execute the same with the Object.groupBy() method. Check them out using
console.time()
andconsole.timeEnd()
.Wait. What? Is there an actual console.time() in JavaScript? Don't mistake me, I'm a beginner though.
I never thought you would wonder on seeing
console.time()
. There are actually many console methods available within JavaScript likeOh my god. There is actually lot to learn 😶🌫️. Hope I would complete them before I complete my UG degree.
Remember, the most successful developers are those who are persistent, curious, and enjoy the process of continuous learning. Celebrate your progress along the way, and don't hesitate to seek help or collaborate with others when needed. Good luck on your learning journey!
How to use a function that doesn't exist and hasn't been implemented. Brilliant.
Your keen observation skills truly deserve a golden star. Despite the desclaimer on the Note section, the context about the compatibility issue seems to be playing hide-and-seek with some of the readers. Btw, thanks for pointing out the concern and I hope my next work will be smoother. Have a happy holiday !!
Y7mate Downloader: you can use websites like This y7mate to download videos quickly and easily. You can also download thumbnails and MP3s from there.
Some comments have been hidden by the post's author - find out more