How to update a value based on previous value in an array of objects?

Viewed 1109

I am trying to update every current value based on the previous value of another property.

Actually, I need to do the task with dates, but just to keep it simple, I'll explain my problem with simple numbers.

I have

  1. Start, which is 1
  2. Stop, which is already defined
  3. End
  4. Duration, which is already defined

My goal

  • Start = Duration of previous index + End of previous index
  • End = Start + Stop of current value

To illustrate, I added the screenshot of the table, how it should look like:

enter image description here

Current view:

enter image description here

Code below and sandbox link

import React from "react";
import { AgGridReact } from "ag-grid-react";
import "./styles.css";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
function App() {
  let start = 1;
  const [gridApi, setGridApi] = React.useState(null);
  const [gridColumnApi, setGridColumnApi] = React.useState(null);

  const onGridReady = (params) => {
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);
  };

  const defaultColDef = {
    flex: 1,
    editable: true
  };

  const columnDefs = [
    {
      headerName: "Name",
      field: "name"
    },
    {
      headerName: "start",
      field: "updatedStart"
    },
    { headerName: "stop", field: "stop" },
    {
      headerName: "end",
      field: "end"
    },
    {
      headerName: "duration",
      field: "duration",
      colId: "duration"
    }
  ];
  const rowData = React.useMemo(
    () => [
      {
        name: "John",
        stop: 10,
        duration: 5
      },
      {
        name: "David",
        stop: 15,
        duration: 8
      },
      {
        name: "Dan",
        stop: 20,
        duration: 6
      }
    ],
    []
  );

  const rowDataWithStart = React.useMemo(() => {
    return (
      rowData &&
      rowData.map((row, i) => ({
        ...row,
        start: start
      }))
    );
  }, [start, rowData]);

  const rowDataWitEnd = React.useMemo(() => {
    return (
      rowDataWithStart &&
      rowDataWithStart.map((row, i) => ({
        ...row,
        end: row.start + row.stop
      }))
    );
  }, [rowDataWithStart]);




//this function(rowDataWitUpdatedStart) should get the previous value of end and duration and get the total
  const rowDataWitUpdatedStart = React.useMemo(() => { 
    return (
      rowDataWitEnd &&
      rowDataWitEnd.map((row, i) => ({
        ...row,
        updatedStart: row.end + row.duration
      }))
    );
  }, [rowDataWitEnd]);

  return (
    <div className="App">
      <h1 align="center">React-App</h1>
      <div className="ag-theme-alpine">
        <AgGridReact
          columnDefs={columnDefs}
          rowData={rowDataWitUpdatedStart}
          defaultColDef={defaultColDef}
          domLayout={"autoHeight"}
          onGridReady={onGridReady}
        ></AgGridReact>
      </div>
    </div>
  );
}

export default App;

I have tried to do the task making a use of valueGetter from the library I use: AG Grid, but seems to be not the best approach. See my previous question Any help will be appreciated

2 Answers

Instead of having a step-wise transformation of your data, you can simply do all the operations you want in one go, using Array.prototype.reduce. The way we are going to use Array.prototype.reduce here is going to be almost similar to how you use Array.prototype.map (we are pushing the objects as-is into a new array, so there is no transformation of data), but the advantage is that we have access to the current index in the third argument, which allows us to access values of the previous item in the accumulator.

Array.prototype.map wouldn't work in this case because in each iteration it will not have access to the updated start in the previous item, unless you have a hack-ish way of storing it outside of the function.

With your logic, where you want start to be the duration + end of the previous item, we can use this logic:

curCopy.start = idx === 0 ? 1 : (acc[idx - 1].duration + acc[idx - 1].end);

...which basically says:

  1. If we are on the first item, just start with the seed value of 1 for start
  2. If not, then we access the previous entry (current index minus one) of the accumulator, and access its duration and end properties. Sum them up and use them for start

Use this logic in a new memoized array of objects returned:

const updatedRowData = React.useMemo(() => {
  const updated = rowData.reduce((acc, cur, idx) => {
    const curCopy =  {...cur};
    const previous = acc[idx - 1];
    curCopy.start = idx === 0 ? 1 : (previous.duration + previous.end);
    curCopy.end = curCopy.start + curCopy.stop;
    acc.push(curCopy);
    return acc;
  }, []);

  return updated;
}, [rowData]);

And then in your template, simply use rowData={updatedRowData}. It should generate the results that you expected:

Expected table results

I have forked your sandbox to create a proof-of-concept example:

Edit ag-grid (forked)

I would loop through all elements, keeping a list of processed entries (result) and just add the start and end onto the item.

(Of course the first one is an anomaly as you need to hardcode the start to be at 1)

    const result = [];
    for (let i = 0; i < rowData.length; i++) {
      const thisValue = rowData[i];
      if (i === 0) {
        const firstValue = { ...thisValue, start: 1, end: 1 + thisValue.stop };
        result.push(firstValue);
      } else {
        const previousValue = result[i - 1];
        const start = previousValue.end + previousValue.duration;
        const end = start + thisValue.stop;
        result.push({ ...thisValue, start, end });
      }
    }

    return result;
  }, [rowData]); 

You can view this on my forked sandbox

Related