Milos
Milos

Reputation: 619

How to group array items properly

I have a table and I want to render headers grouped by group property from object in array. The array with the headers looks like this:

const headers = [
    { label: 'Date', group: '' },
    { label: 'Event', group: '' },
    { label: 'Days Out', group: '' },
    { label: 'T', group: 'Sales Velocity' },
    { label: '-1', group: 'Sales Velocity' },
    { label: '-2', group: 'Sales Velocity' },
    { label: '-3', group: 'Sales Velocity' },
    { label: '-4', group: 'Sales Velocity' },
    { label: 'Sold Last 5', group: 'Ticket Sales' },
    { label: 'Total Sold', group: 'Ticket Sales' },
    { label: 'Sellable cap.', group: 'Ticket Sales' },
    { label: '% sold', group: 'Ticket Sales' },
    { label: 'Availab.', group: 'Ticket Sales' },
    { label: 'Total', group: 'Revenue' },
    { label: 'Holds', group: 'Inventory Status' },
    { label: 'Comp', group: 'Inventory Status' },
    { label: 'Open', group: 'Inventory Status' },
    { label: 'Price cat.', group: 'Inventory Status' },
    { label: 'Avg. price', group: 'Stats' },
    { label: 'First time %', group: 'Stats' },
];

and the table component looks like this:

<TableHead>
    <TableRow>
      {headers.map((header, index) => (
        <TableCellASHeader key={index}>
          <TableSortLabel
            active={header.column === sorting.orderBy}
            direction={sorting.orderDirection}
            onClick={() => onClickSort(header.column)}
            IconComponent={ArrowDropDownIcon}
          >
            {/* {header.group} */} // here I need to render group but only once per group
            {header.label}
          </TableSortLabel>
        </TableCellASHeader>
      ))}
    </TableRow>
  </TableHead>

What I want is to render header.group above header.label but only once per group, like on this picture below. Any code sample will be appreciated. enter image description here

Upvotes: 0

Views: 229

Answers (4)

himayan
himayan

Reputation: 842

First of all, I would club the headers into an object based on group.

const groupedHeaders = headers.reduce((acc, curr) => {
  let {group, label} = curr;
  if (!group) group = 'empty';
    if (!acc[group]) {
        acc[group] = [label]
    } else {
        acc[group] = [...acc[group], label]
    }
  return acc;
}, {});

After clubbing them up, the groupedHeaders would look like this -

{
  empty: [ 'Date', 'Event', 'Days Out' ],
  'Sales Velocity': [ 'T', '-1', '-2', '-3', '-4' ],
  'Ticket Sales': [
    'Sold Last 5',
    'Total Sold',
    'Sellable cap.',
    '% sold',
    'Availab.'
  ],
  Revenue: [ 'Total' ],
  'Inventory Status': [ 'Holds', 'Comp', 'Open', 'Price cat.' ],
  Stats: [ 'Avg. price', 'First time %' ]
}

Rendering in React part would make use of Object.entries() to iterate through the object and display accordingly.

Object.entries(groupedHeaders).map(([group, labels]) => {
  <TableRow>
      <HeaderGroup> // you will need to add css with flex/grid
         {group === 'empty' ? <EmptyHeader /> : <GroupHeader />}
         <SubHeaderGroup> // css required for grouping
           {labels.map((header, index) => (
             <TableCellASHeader key={index}>
               <TableSortLabel
                 active={header.column === sorting.orderBy}
                 ....
             </TableCellASHeader>
           ))}
         </SubHeaderGroup>
      </YourHeaderGroup>
   </TableRow>
});

Check out this Code Sandbox link for the full version of code.

Upvotes: 2

grzesiekmq
grzesiekmq

Reputation: 439

for example this code with filter and map and optionally put arrays into object

const headers = [{
    label: 'Date',
    group: ''
  },
  {
    label: 'Event',
    group: ''
  },
  {
    label: 'Days Out',
    group: ''
  },
  {
    label: 'T',
    group: 'Sales Velocity'
  },
  {
    label: '-1',
    group: 'Sales Velocity'
  },
  {
    label: '-2',
    group: 'Sales Velocity'
  },
  {
    label: '-3',
    group: 'Sales Velocity'
  },
  {
    label: '-4',
    group: 'Sales Velocity'
  },
  {
    label: 'Sold Last 5',
    group: 'Ticket Sales'
  },
  {
    label: 'Total Sold',
    group: 'Ticket Sales'
  },
  {
    label: 'Sellable cap.',
    group: 'Ticket Sales'
  },
  {
    label: '% sold',
    group: 'Ticket Sales'
  },
  {
    label: 'Availab.',
    group: 'Ticket Sales'
  },
  {
    label: 'Total',
    group: 'Revenue'
  },
  {
    label: 'Holds',
    group: 'Inventory Status'
  },
  {
    label: 'Comp',
    group: 'Inventory Status'
  },
  {
    label: 'Open',
    group: 'Inventory Status'
  },
  {
    label: 'Price cat.',
    group: 'Inventory Status'
  },
  {
    label: 'Avg. price',
    group: 'Stats'
  },
  {
    label: 'First time %',
    group: 'Stats'
  },
];
const empty = headers.filter(el => el.group === '')
  .map(el => el.label)
console.log('empty', empty)
const sales = headers.filter(el => el.group === 'Sales Velocity')
  .map(el => el.label)
console.log('sales', sales)
const ticket = headers.filter(el => el.group === 'Ticket Sales')
  .map(el => el.label)
console.log('ticket', ticket)
const revenue = headers.filter(el => el.group === 'Revenue')
  .map(el => el.label)
console.log('revenue', revenue)
const inventory = headers.filter(el => el.group === 'Inventory Status')
  .map(el => el.label)
console.log('inventory', inventory)
const stats = headers.filter(el => el.group === 'Stats')
  .map(el => el.label)
console.log('stats', stats)

const obj = {}
obj.empty = empty
obj.sales = sales
obj.ticket = ticket
obj.revenue = revenue
obj.inventory = inventory
obj.stats = stats

console.log('obj', obj)

Upvotes: 0

techie_questie
techie_questie

Reputation: 1522

You can alternatively use lodash/uniqBy to achieve this. If you are working on enterprise level application and using React/Angular, it’s worth considering. You can achieve the desired result in one line like below-

import uniqBy from ‘lodash/uniqBy’;

const modifiedArray = uniqBy(headers,’group’);

Also, it is always recommended to check beforehand if your data is undefined or not. So, the best way to do is like below-

import get from ‘lodash/get’;

const modifiedArray = uniqBy((get(headers, ‘response.data’, [])),’group’);

Considering headers array is part of your api and the path is response.data.headers.

Please Note: You need to install lodash as your dev dependency in your project. uniqBy method will show only unique values without any duplication.

Upvotes: 0

SHUBHAM JAIN
SHUBHAM JAIN

Reputation: 44

you can use new Set() and store group name in that and check each time when map function run. I have done this in javascript it will print group name 1 time and label many times., Check this

const headers = [
    { label: 'Date', group: '' },
    { label: 'Event', group: '' },
    { label: 'Days Out', group: '' },
    { label: 'T', group: 'Sales Velocity' },
    { label: '-1', group: 'Sales Velocity' },
    { label: '-2', group: 'Sales Velocity' },
    { label: '-3', group: 'Sales Velocity' },
    { label: '-4', group: 'Sales Velocity' },
    { label: 'Sold Last 5', group: 'Ticket Sales' },
    { label: 'Total Sold', group: 'Ticket Sales' },
    { label: 'Sellable cap.', group: 'Ticket Sales' },
    { label: '% sold', group: 'Ticket Sales' },
    { label: 'Availab.', group: 'Ticket Sales' },
    { label: 'Total', group: 'Revenue' },
    { label: 'Holds', group: 'Inventory Status' },
    { label: 'Comp', group: 'Inventory Status' },
    { label: 'Open', group: 'Inventory Status' },
    { label: 'Price cat.', group: 'Inventory Status' },
    { label: 'Avg. price', group: 'Stats' },
    { label: 'First time %', group: 'Stats' },
];

let groups = new Set();
headers.map((header) => {
    if(!groups.has(header.group)){
      groups.add(header.group);
      console.log(header.group); 
    } 
    console.log(header.label);
})

Output

Date
Event
Days Out
Sales Velocity
T
-1
-2
-3
-4
Ticket Sales
Sold Last 5
Total Sold
Sellable cap.
% sold
Availab.
Revenue
Total
Inventory Status
Holds
Comp
Open
Price cat.
Stats
Avg. price
First time %

Upvotes: 0

Related Questions