Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

array.groupBy in TypeScript

The basic array class has .map, .forEach, .filter, and .reduce, but .groupBy i noticably absent, preventing me from doing something like

const MyComponent = (props:any) => {
    return (
        <div>
            {
                props.tags
                .groupBy((t)=>t.category_name)
                .map((group)=>{
                    [...]
                })
            }

        </div>
    )
}

I ended up implementing something myself:

class Group<T> {
    key:string;
    members:T[] = [];
    constructor(key:string) {
        this.key = key;
    }
}


function groupBy<T>(list:T[], func:(x:T)=>string): Group<T>[] {
    let res:Group<T>[] = [];
    let group:Group<T> = null;
    list.forEach((o)=>{
        let groupName = func(o);
        if (group === null) {
            group = new Group<T>(groupName);
        }
        if (groupName != group.key) {
            res.push(group);
            group = new Group<T>(groupName);
        }
        group.members.push(o)
    });
    if (group != null) {
        res.push(group);
    }
    return res
}

So now I can do

const MyComponent = (props:any) => {
    return (
        <div>
            {
                groupBy(props.tags, (t)=>t.category_name)
                .map((group)=>{
                    return (
                        <ul key={group.key}>
                            <li>{group.key}</li>
                            <ul>
                                {
                                    group.members.map((tag)=>{
                                        return <li key={tag.id}>{tag.name}</li>
                                    })
                                }
                            </ul>
                        </ul>
                    )
                })
            }

        </div>
    )
}

Works pretty well, but it is too bad that I need to wrap the list rather than just being able to chain method calls.

Is there a better solution?

like image 305
Eldamir Avatar asked Feb 09 '17 11:02

Eldamir


People also ask

How to group by array in JavaScript?

The group() method groups the elements of the calling array according to the string values returned by a provided testing function. The returned object has separate properties for each group, containing arrays with the elements in the group.

How do you group objects in an array?

The most efficient method to group by a key on an array of objects in js is to use the reduce function. The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.

How to group objects in JavaScript?

const arr = [ {food: 'apple', type: 'fruit'}, {food: 'potato', type: 'vegetable'}, {food: 'banana', type: 'fruit'}, ]; We are required to write a JavaScript function that takes in one such array. Our function should then group the array objects based on the "type" property of the objects.


2 Answers

You can use the following code to group stuff using Typescript.

const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
  list.reduce((previous, currentItem) => {
    const group = getKey(currentItem);
    if (!previous[group]) previous[group] = [];
    previous[group].push(currentItem);
    return previous;
  }, {} as Record<K, T[]>);

So, if you have the following structure and array:

type Person = {
  name: string;
  age: number;
};

const people: Person[] = [
  {
    name: "Kevin R",
    age: 25,
  },
  {
    name: "Susan S",
    age: 18,
  },
  {
    name: "Julia J",
    age: 18,
  },
  {
    name: "Sarah C",
    age: 25,
  },
];

You can invoke it like:

const results = groupBy(people, i => i.name);

Which in this case, will give you an object with string keys, and Person[] values.

There are a few key concepts here:

1- You can use function to get the key, this way you can use TS infer capabilities to avoid having to type the generic every time you use the function.

2- By using the K extends keyof any type constraint, you're telling TS that the key being used needs to be something that can be a key string | number | symbol, that way you can use the getKey function to convert Date objects into strings for example.

3- Finally, you will be getting an object with keys of the type of the key, and values of the of the array type.

like image 104
kevinrodriguez-io Avatar answered Oct 06 '22 20:10

kevinrodriguez-io


you could add the function to the array prototype in your app (note some don't recomend this: Why is extending native objects a bad practice?):

Array.prototype.groupBy = function(/* params here */) { 
   let array = this; 
   let result;
   /* do more stuff here*/
   return result;
}; 

Then create an interface in typescript like this:

.d.ts version:

    interface Array<T>
    {
        groupBy<T>(func:(x:T) => string): Group<T>[]
    }

OR in a normal ts file:

declare global {
   interface Array<T>
   {
      groupBy<T>(func:(x:T) => string): Group<T>[]
   }
}

Then you can use:

 props.tags.groupBy((t)=>t.category_name)
     .map((group)=>{
                    [...]
                })
like image 28
Andrew Monks Avatar answered Oct 06 '22 19:10

Andrew Monks