How to restructure and map an array to fit into a specified data structure in React

I am using a reaviz is a charting tool for React. I am creating a habit tracker app and want to visually display the data on a series bar graph for the last week of user inputs. I have my data from my database in a structure like this:

habits = [
 { 
   habit: "Cook Dinner",
   inputs: [
     {
      input: 'Success',
      date: 'Oct 5th 21'
     },
     {
      input: 'Fail',
      date: 'Oct 6th 21'
     },
     {
      input: 'Skip',
      date: 'Oct 7th 21'
     }]
  },
  { 
   habit: "Clean House",
   inputs: [
     {
      input: 'Fail',
      date: 'Oct 5th 21'
     },
     {
      input: 'Fail',
      date: 'Oct 6th 21'
     },
     {
      input: 'Fail',
      date: 'Oct 7th 21'
    }]
  },
  { 
   habit: "Cook Dinner",
     inputs: [
     {
      input: 'Success',
      date: 'Oct 5th 21'
     },
     {
      input: 'Fail',
      date: 'Oct 6th 21'
     },
     {
      input: 'Skip',
      date: 'Oct 7th 21'
     }]
   }]

So now I would like to figure out a way to map my data to the data structure I need for the chart for example the output would be something like this for the above data. I will need to count how many inputs fall on that day for either, success, fail, or skip:

habitsMappedData = [
     {
    key: 'Oct 5th 21',
    data: [
      {
        key: 'Success',
        data: 2
      },
      {
        key: 'Fail',
        data: 1
      },
      {
        key: 'Skip',
        data: 0
      }
    ]
  },  {
    key: 'Oct 6th 21',
    data: [
      {
        key: 'Sucess',
        data: 0
      },
      {
        key: 'Fail',
        data: 3
      },
      {
        key: 'Skip',
        data: 0
      }
    ]
  },  {
    key: 'Oct 7th 21',
    data: [
      {
        key: 'Success',
        data: 0
      },
      {
        key: 'Fail',
        data: 1
      },
      {
        key: 'Skip',
        data: 2
      }
    ]
  },
]

Really appreciate you taking a look through this. If any clarifications are required please let me know.

Answer

This is really just a ‘group-by’ operation by date, but because of the nested arrays it’s a little more involved.

The below snippet uses a reduce() call to accumulate into an object using date as the grouping property, and summing using input as a secondary property. To convert the resulting object back to an array of nested objects it calls Array.from() on the Object.entries() of the returned object and maps the key and data properties using the map provided as the second parameter of Array.from(). The nested data objects are generated using a final map() call.

const habits = [ { habit: 'Cook Dinner', inputs: [ { input: 'Success', date: 'Oct 5th 21', }, { input: 'Fail', date: 'Oct 6th 21', }, { input: 'Skip', date: 'Oct 7th 21', }, ], }, { habit: 'Clean House', inputs: [ { input: 'Fail', date: 'Oct 5th 21', }, { input: 'Fail', date: 'Oct 6th 21', }, { input: 'Fail', date: 'Oct 7th 21', }, ], }, { habit: 'Cook Dinner', inputs: [ { input: 'Success', date: 'Oct 5th 21', }, { input: 'Fail', date: 'Oct 6th 21', }, { input: 'Skip', date: 'Oct 7th 21', }, ], }, ];

const habitsMappedData = Array.from(
  Object.entries(
    habits.reduce((a, { inputs }) => {
      for (const { date, input } of inputs) {
        (a[date] ??= { Success: 0, Fail: 0, Skip: 0 })[input] += 1;
      }

      return a;
    }, {})
  ),
  ([key, _data]) => ({
    key,
    data: Object.entries(_data).map(([key, data]) => ({ key, data })),
  })
);

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

Note: the above relies on the logical nullish assignment operator (??=) which can be replaced with an OR short-circuit if needed for compatibility.

for (const { date, input } of inputs) {
  (a[date] || (a[date] = { Success: 0, Fail: 0, Skip: 0 }))[input] += 1;
}

or written out explicitly in an if

for (const { date, input } of inputs) {
  if (a[date] === undefined) {
    a[date] = { Success: 0, Fail: 0, Skip: 0 };
  }
  a[date][input] += 1;
}