Kyle Klaus
Kyle Klaus

Reputation: 23

Transform array of data into grouped data for SectionList component

I'll freely admit that Javascript is not my strongest language, and React Native is very new, so, there may be an obviously easy way to do this that I'm not seeing.

I've got an API that presents some transaction data in a simple structure:

[
  {
    "id": 1,
    "title": "Apple Store",
    "date": "2021-09-10",
    "amount": "$100.00",
  },
  {
    "id": 41,
    "title": "Zulauf, Walter and Metz",
    "date": "2021-09-10",
    "amount": "$14.00",
  },
  {
    "id": 9,
    "title": "Aufderhar PLC",
    "date": "2021-09-09",
    "amount": "$78.00",
  },
  {
    "id": 10,
    "title": "Bayer and Sons",
    "date": "2021-09-07",
    "amount": "$67.00",
  }
]

I want to present this data using a SectionList component, with the transactions in sections by date. My (likely crude) attempt to solve this was going to be to transform this data into the following structure:

[
  {
    "date": "2021-09-10",
    "transactions": [
      {
        "id": 1,
        "title": "Apple Store",
        "date": "2021-09-10",
        "amount": "$100.00",
      },
      {
        "id": 41,
        "title": "Zulauf, Walter and Metz",
        "date": "2021-09-10",
        "amount": "$14.00",
      }
    ]
  },
  {
    "date": "2021-09-09",
    "transactions": [
      {
        "id": 9,
        "title": "Aufderhar PLC",
        "date": "2021-09-09",
        "amount": "$78.00",
      }
    ]
  },
  {
    "date": "2021-09-07",
    "transactions": [
      {
        "id": 10,
        "title": "Bayer and Sons",
        "date": "2021-09-07",
        "amount": "$67.00",
      }
    ]
  }
]

But I'm honestly lost as to how to transform this data (or if there's a better way to solve this problem). I started by using Lodash's groupBy function, which seemed promising, but it looks like SectionList doesn't want an object, it wants an array.

Transforming the output of groupBy into an array straight off drops the keys and I've got grouped data but no clear value for the section header.

Again, there's probably some deviously simple way to address this, data comes in as a flat array all the time. I appreciate any guidance, assistance, or examples anybody can point me to.

Upvotes: 2

Views: 1902

Answers (6)

metaperture
metaperture

Reputation: 2463

Here's a TypeScript utility function that I've written to do something similar:

// from https://stackoverflow.com/a/49752227
type KeyOfType<T, V> = keyof {
  [P in keyof T as T[P] extends V? P: never]: any
};

// data[key] must be string, number, or symbol
export function groupBy<T>(data: T[], key: KeyOfType<T, string | number | symbol>) {
  const collect: {[index: string | number | symbol]: T[]} = {};
  for (let item of data) {
    const key_value = item[key] as string | number | symbol;
    if (key_value in collect) collect[key_value].push(item);
    else collect[key_value] = [item];
  }
  return collect;
};

On the above data:

const data = [
  {id: 1, title: 'Apple Store', date: '2021-09-10', amount: '$100.00'},
  {id: 41, title: 'Zulauf, Walter and Metz', date: '2021-09-10', amount: '$14.00'},
  {id: 9, title: 'Aufderhar PLC', date: '2021-09-09', amount: '$78.00'},
  {id: 10, title: 'Bayer and Sons', date: '2021-09-07', amount: '$67.00'},
];
groupBy(data, 'date');
// yields:
{
  '2021-09-07': [{id: 10, title: 'Bayer and Sons', date: '2021-09-07', amount: '$67.00'}],
  '2021-09-09': [{id: 9, title: 'Aufderhar PLC', date: '2021-09-09', amount: '$78.00'}],
  '2021-09-10': [
    {id: 1, title: 'Apple Store', date: '2021-09-10', amount: '$100.00'},
    {id: 41, title: 'Zulauf, Walter and Metz', date: '2021-09-10', amount: '$14.00'},
  ],
};

For multiple keys:

type Indexable = string | number | symbol;

export function multiGroupBy<T>(data: T[], ...keys: KeyOfType<T, Indexable>[]) {
  const collect_keys: Indexable[][] = [];
  const collect_values: T[][] = [];
  for (let item of data) {
    const key_value: Indexable[] = [];
    for (let key of keys) {
      key_value.push(item[key] as Indexable);
    }
    let key_ix = collect_keys.indexOf(key_value);
    if (key_ix == -1) {
      key_ix = collect_keys.length;
      collect_keys.push(key_value);
      collect_values.push([item]);
    } else collect_values[key_ix].push(item);
  }
  return [collect_keys, collect_values];
}

Upvotes: 0

Hamed
Hamed

Reputation: 427

you could use loadash

var result = _(data)
    .groupBy(item => item.date)
    .map((value, key) => ({date: key, transactions: value}))
    .value();

Upvotes: 0

Ori Drori
Ori Drori

Reputation: 193037

With lodash you can group by the date then map to the required form:

const input = [{"id":1,"title":"Apple Store","date":"2021-09-10","amount":"$100.00"},{"id":41,"title":"Zulauf, Walter and Metz","date":"2021-09-10","amount":"$14.00"},{"id":9,"title":"Aufderhar PLC","date":"2021-09-09","amount":"$78.00"},{"id":10,"title":"Bayer and Sons","date":"2021-09-07","amount":"$67.00"}];

const result = _.map(
  _.groupBy(input, 'date'),
  (transactions, date) => ({ date, transactions })
)

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Upvotes: 0

Mister Jojo
Mister Jojo

Reputation: 22412

simply

const data = 
  [ { id:  1, title: 'Apple Store',             date: '2021-09-10', amount: '$100.00' } 
  , { id: 41, title: 'Zulauf, Walter and Metz', date: '2021-09-10', amount:  '$14.00' } 
  , { id:  9, title: 'Aufderhar PLC',           date: '2021-09-09', amount:  '$78.00' } 
  , { id: 10, title: 'Bayer and Sons',          date: '2021-09-07', amount:  '$67.00' } 
  ] 

const res = Object.entries(data.reduce((r,{id,title,date,amount})=>
  {
  r[date] = r[date] ?? []
  r[date].push({id,title,date,amount})
  return r
  },{})).map(([k,v])=>({date:k,transactions:v}))

console.log( res )
.as-console-wrapper { max-height: 100% !important; top: 0 }

Upvotes: 0

Isaac
Isaac

Reputation: 12894

const input = [
  {
    "id": 1,
    "title": "Apple Store",
    "date": "2021-09-10",
    "amount": "$100.00",
  },
  {
    "id": 41,
    "title": "Zulauf, Walter and Metz",
    "date": "2021-09-10",
    "amount": "$14.00",
  },
  {
    "id": 9,
    "title": "Aufderhar PLC",
    "date": "2021-09-09",
    "amount": "$78.00",
  },
  {
    "id": 10,
    "title": "Bayer and Sons",
    "date": "2021-09-07",
    "amount": "$67.00",
  }
]

const result = input.reduce((accum, current)=> {
  let dateGroup = accum.find(x => x.date === current.date);
  if(!dateGroup) {
    dateGroup = { date: current.date, transactions: [] }
    accum.push(dateGroup);
  }
  dateGroup.transactions.push(current);
  return accum;
}, []);

console.log(result)

Given an array, whenever your result is expecting to have same number of elements, use map, but since your result has different number of elements, use reduce as shown above. The idea is by having reduce, loop over each element, see if you can find the element, and push the current element into the list

Upvotes: 5

Tuan Dao
Tuan Dao

Reputation: 2815

The lodash groupBy just helps you with group data, you should process grouped data by converting it into your format.

const input = [
  {
    "id": 1,
    "title": "Apple Store",
    "date": "2021-09-10",
    "amount": "$100.00",
  },
  {
    "id": 41,
    "title": "Zulauf, Walter and Metz",
    "date": "2021-09-10",
    "amount": "$14.00",
  },
  {
    "id": 9,
    "title": "Aufderhar PLC",
    "date": "2021-09-09",
    "amount": "$78.00",
  },
  {
    "id": 10,
    "title": "Bayer and Sons",
    "date": "2021-09-07",
    "amount": "$67.00",
  }
];

const groupedArray = _.groupBy(input, "date");

let result = [];
for (const [key, value] of Object.entries(groupedArray)) {
   result.push({
     'date': key,
     'transactions': value
   })
}

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

Upvotes: 0

Related Questions