Jakub
Jakub

Reputation: 2709

Improve and simplify a JS reduce method using lodash or some modern method to have better readability of the code

I built the following method using reduce to reduce data from DB to a better shape for my needs and would like to know how to simplify it and make it better

const dbData = [{
    studyId: "X",
    siteId: "A",
    day: "2000-01-01",
    status: "PENDING_CALLCENTER",
    current: 5,
    total: 17
  },
  {
    studyId: "X",
    siteId: "A",
    day: "2000-01-01",
    status: "PENDING_SITE",
    current: 3,
    total: 9
  },
  {
    studyId: "Y",
    siteId: "B",
    day: "2000-01-01",
    status: "PENDING_SITE",
    current: 3,
    total: 9
  },
  {
    studyId: "Y",
    siteId: "B",
    day: "2000-01-01",
    status: "PENDING_CALLCENTER",
    current: 3,
    total: 9
  }
];

const reduced = dbData.reduce((acc, row) => {
  const {
    studyId,
    siteId,
    status,
    current,
    total
  } = row;
  const idx = acc.findIndex(
    (x) => studyId === x.studyId && siteId === x.siteId
  );
  const item =
    idx === -1 ?
    {
      studyId,
      siteId,
      currents: {},
      totals: {}
    } :
    { ...acc[idx]
    };
  item.currents[status] = item.currents[status] ?
    item.currents[status] + current :
    current;
  item.totals[status] = item.totals[status] ?
    item.totals[status] + total :
    total;
  if (idx === -1) {
    acc.push(item);
  } else {
    acc[idx] = item;
  }
  return acc;
}, []);

console.log(reduced);

The method gets data like from the example an array of objects where we have as a duplicate the same studyId and siteId and that data contains always a different status.

The method needs to aggregate this data in one object per the same studyId and siteId like the result of the snippet including all the statuses divided by currents and totals.

As a simple example, we will never have the same data for status for that study and site plus the current and total are related to that status only

{
studyId: A
siteId: 1
status: PENDING_CALLCENTER
current: 1
total: 1
}
{
studyId: A
siteId: 1
status: PENDING_SITE
current: 3
total: 5
}
{
studyId: A
siteId: 1
status: ANOTHER_STATUS
current: 34
total: 50
}

The above will be resulting as follow

{
studyId: A
siteId: 1
currents: [{
   PENDING_CALLCENTER: 1
   PENDING_SITE: 3
   ANOTHER_STATUS: 34  
}]
totals: [{
   PENDING_CALLCENTER: 1
   PENDING_SITE: 5
   ANOTHER_STATUS: 50  
}]
}

In this way, that data is altogether making it easier to use in further steps.

What I need to understand is how can I make this better as I do not really like the way I found it with reduce, for example, that reduce can be improved or can be used lodash to get same result or another method approach.

Upvotes: 0

Views: 105

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370699

Instead of using findIndex with the accumulator being an array, you can have an object instead, indexed by a unique key for a given combination of a studyId and siteId.

Instead of the conditional operator to check if the nested value exists when trying to add, you can alternate the existing value ?? 0 so that it defaults to 0 if the value isn't found, which makes the syntax a bit easier.

const dbData=[{studyId:"X",siteId:"A",day:"2000-01-01",status:"PENDING_CALLCENTER",current:5,total:17},{studyId:"X",siteId:"A",day:"2000-01-01",status:"PENDING_SITE",current:3,total:9},{studyId:"Y",siteId:"B",day:"2000-01-01",status:"PENDING_SITE",current:3,total:9},{studyId:"Y",siteId:"B",day:"2000-01-01",status:"PENDING_CALLCENTER",current:3,total:9}];

const dataByKey = {};
for (const { studyId, siteId, status, current, total } of dbData) {
  const key = `${studyId}_${siteId}`;
  const thisData = dataByKey[key] ??= {
    studyId,
    siteId,
    currents: {},
    totals: {}
  };
  thisData.currents[status] = (thisData.currents[status] ?? 0) + current;
  thisData.totals[status] = (thisData.totals[status] ?? 0) + total;
}

console.log(Object.values(dataByKey));

A semi-common pattern I see some people use, that I think is a slight mistake, is to use .reduce when transforming an array into an object (or into an array that isn't one-to-one or a filter of the original array). IMO, using a plain loop is both easier to read and easier to write in most circumstances. See: Is reduce bad?

Upvotes: 2

Related Questions