1

I have created a table in React. I use javascript to map through an object that contains all the information I need to render a dynamic table. A couple of my columns contain numbers, and I want to sum those columns up to a given point in an additional column. Below is an example of my table.

Example of the desired table

The idea is that the balance column will total the entire debit column up to that point. So the balance cell on the first row should say 50 (like it does). However, the second cell in the balance column would need to say 100 (the sum of all cells in the debit column up to that point). Here is the react code I am using to map through the object (which is stored in state) to populate the table. Please note that the balance column is not stored in the object...I am attempting to have this column values created within the map function somehow.

<table id="transactionDetailDisplay">
        <tr>
            <th>#</th>
            <th>Account</th>
            <th>Description</th>
            <th>Debit</th>
            <th>Credit</th>
            <th>Balance</th>
            </tr>       
    {this.state.transactionObject.map((item =>
        <tr>
            <td>{}</td>
            <td>{item.AccountID}</td>
            <td>{item.Description}</td>
            <td>{item.Debit}</td>
            <td>{item.Credit}</td>
            <td>{item.Debit}</td>
        </tr>))}
    </table>

What I would like to do is add a variable within my map function to serve as an accumulator. For example, rather than stating the last table data item as item.Debit, I would like to store item.Debit in a variable, then put that variable value in the table data line. With each cycle of the mapping function, I would add item.Debit to the variable to accumulate the correct sum to be displayed in Balance. However, I am having trouble figuring out the proper javascript to create a variable accumulator within the mapping function and / or the table tag. I think that since it is within the table tag, I cannot just define a variable like I could elsewhere. Is there a way I can do this, maybe with special syntax? Does anyone know any tricks to help me out? Thanks so much for any ideas!!

Luke Sharon
  • 140
  • 1
  • 7
  • 22

2 Answers2

1

You can create a variable beforehand that you add to inside the map callback.

{
    (() => {
        let debit = 0;
        return this.state.transactionObject.map((item) => {
            debit += item.Debit;
            return (
                <tr>
                    <td>{ }</td>
                    <td>{item.AccountID}</td>
                    <td>{item.Description}</td>
                    <td>{item.Debit}</td>
                    <td>{item.Credit}</td>
                    <td>{debit}</td>
                </tr>);
        })
    })()
}

You could also consider creating an array of debits before rendering.

let debit = 0;
const debits = this.state.transactionObject.map(item => debit += item.debit);
{
    this.state.transactionObject.map((item, i) =>
        <tr>
            <td>{ }</td>
            <td>{item.AccountID}</td>
            <td>{item.Description}</td>
            <td>{item.Debit}</td>
            <td>{item.Credit}</td>
            <td>{debits[i]}</td>
        </tr>
    )
}

You could make it functional by slicing the array up to that point and adding it up, but this increases the complexity to O(n ^ 2).

const debits = this.state.transactionObject.map((_, i, arr) => (
    arr.slice(0, i + 1).reduce((a, b) => a + b)
));
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Thanks so much!! All those return statements and syntax rules you included seem very confusing to me and are WAY out of my league (however it DID work beautifully!). Are you familiar with any resources that explain all this weird-looking syntax that you could direct me to? – Luke Sharon Mar 26 '21 at 03:08
  • 1
    The IIFE `(() => { })()` is there to create a scope in which variables can be declared in. It's needed so that `let debit = 0;` can be initialized, otherwise the rolling value can't be created. https://stackoverflow.com/questions/8228281/what-is-the-function-construct-in-javascript I'm not sure what other weird syntax you're referring to. – CertainPerformance Mar 26 '21 at 03:10
1

Array.prototype.map function callback should be a pure function, i.e. without side-effects like mutating a balance value. Use Array.prototype.reduce to reduce an array down to a single value.

const balance = this.state.transactionObject.reduce(
  (balance, { Debit }) => balance + Debit,
  0,
);

If you need to compute multiple totals, you can reduce down to an object with different properties representing the totals you need, this way you still only iterate the array once to compute them.

Edit

I think I initially misunderstood your request, you want to display an accumulated balance up to the current row.

Pre-compute the balance per row first.

const arr = this.state.transactionObject.map((item, index, arr) => ({
  ...item,
  Balance: arr[index - 1]?.Balance ?? 0) + item.Debit ?? 0
}));

...

{arr.map((item =>
  <tr>
    <td>{}</td>
    <td>{item.AccountID}</td>
    <td>{item.Description}</td>
    <td>{item.Debit}</td>
    <td>{item.Credit}</td>
    <td>{item.Balance}</td>
  </tr>)
)}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181