React app re-rendering error

Hello,

Sorry this might be a lot but I’m at my minds end.

I am writing a budgeting app in React and I have run into the re-rendering error. I can’t figure out what is tripping it.

If anyone could look this over and give some ideas or React best practice advice that would be awesome.

There is a batch function that I added in because GPT recommended it but not sure if it knows what it’s talking about haha.

I know this code isn’t perfect React code. Still learning!

import "./styles.css";

import { useState, useEffect } from "react";
import { unstable_batchedUpdates as batch } from 'react-dom';

function CategoryAmount({ parentCallback, idval }) {
  return (
    <input
      className="categorybox"
      placeholder="Budgeted"
      id="categoryamount"
      onChange={(event) => parentCallback(event, idval)}
    />
  );
}

function CategoryName({ categname, idval }) {
  return (
    <input
      placeholder="Category"
      onChange={(event) => categname(event, idval)}
    />
  );
}

function AmountBox({ Numvalue, Spent }) {
  const [numval, setNumval] = useState(Numvalue);

  useEffect(() => {
    setNumval(Numvalue);
  }, [Numvalue]);
  return (
    <div className="amount-children" id="amountbox">
      ¥{(numval - Spent).toLocaleString()}
    </div>
  );
}

function NewBox({ handleClick, title }) {
  return <button onClick={handleClick}>{title}</button>;
}

function TransCat({ categories, change, index }) {
  return (
    <select
      name="dropdown"
      className="options"
      onChange={(event) => change(event, index)}
    >
      <option className="firstoption"></option>
      {categories.map((item, index) => (
        <option value={item[0]}>{item[0]}</option>
      ))}
    </select>
  );
}

export default function App() {
  //boxvlue = [category, budgetamount, spent]
  const [boxvalue, setBoxvalue] = useState(Array(3).fill(["", 0, 0]));
  const [total, setTotal] = useState(0);
  //transaction = [category, expense, income]
  const [transaction, setTransaction] = useState(Array(5).fill(["", 0, 0]));

  function modifyNum(arr, filterArr) {
    for (let j = 0; j < arr.length; j++) {
      if (arr[j].match(/\d/)) {
        filterArr.push(arr[j]);
      }
    }

    const boxvalstr = filterArr.join("");
    return boxvalstr;
  }

  function handleInput(event, id) {
    const nextBoxVal = boxvalue.slice();
    let val = event.target.value;
    const nextTransaction = transaction.slice();


    batch(()=> {
      //out transactions
      if (event.target.id === "out") {
        //if OUT then cross check category names and change boxvalue?
        //This LOOP formats transaction numbers
        for (let index = 0; index < nextTransaction.length; index++) {
          if (id === index) {
            const arr = [...val];
            const filterArr = [];
            const boxstr = modifyNum(arr, filterArr);
            const str = boxstr.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
            event.target.value = "¥" + str;
            nextTransaction[index] = [
              nextTransaction[index][0],
              parseFloat(boxstr),
              nextTransaction[index][2]
            ];
          }
        }
        setTransaction(prevTransaction=> [...prevTransaction, [
          nextTransaction[id]
        ]]);
        console.log(transaction)
      } else if (event.target.id === "in") {
        for (let index = 0; index < nextTransaction.length; index++) {
          let added = 0;
          if (id === index) {
            const arr = [...val];
            const filterArr = [];
            const boxstr = modifyNum(arr, filterArr);
            const str = boxstr.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
            event.target.value = "¥" + str;
            nextTransaction[index] = [
              nextTransaction[index][0],
              nextTransaction[index][1],
              parseFloat(boxstr)
            ];
            for (let ii = 0; ii < boxvalue.length; ii++) {
              if (nextBoxVal[ii][0] === nextTransaction[index][0]) {
                nextBoxVal[ii] = [nextBoxVal[ii][0], nextBoxVal[ii][1], -added];
                setBoxvalue(nextBoxVal);
              }
            }
          }
        }
        setTransaction(prevTransaction=> [...prevTransaction, [
          nextTransaction[id]
        ]]);
      } else {
        let tot = 0;
        for (let i = 0; i < nextBoxVal.length; i++) {
          if (i === id) {
            const arr = [...val];
            const filterArr = [];
            const boxstr = modifyNum(arr, filterArr);
            const str = boxstr.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
            event.target.value = "¥" + str;
            nextBoxVal[i] = [
              nextBoxVal[i][0],
              parseFloat(boxstr),
              nextBoxVal[i][2]
            ];
          }
          tot += nextBoxVal[i][1];
        }
        setBoxvalue(nextBoxVal);
        setTotal(tot);
      }

      for (let x = 0; x < nextBoxVal.length; x++) {
        let spent = 0;
        for (let ii = 0; ii < nextTransaction.length; ii++) {
          if (
            nextBoxVal[x][0] === nextTransaction[ii][0] &&
            nextTransaction[ii][1] > 0
          ) {
            spent += nextTransaction[ii][1];
          } else if (
            nextBoxVal[x][0] === nextTransaction[ii][0] &&
            nextTransaction[ii][2] > 0
          ) {
            spent -= nextTransaction[ii][2];
          }
        }
        nextBoxVal[x] = [nextBoxVal[x][0], nextBoxVal[x][1], spent];
        setBoxvalue(nextBoxVal);
      }




    })
    
  }

  function handleCatName(event, id) {
    // want to save name to database. This isnt working though?

    const nextBox = boxvalue.slice();
    for (let i = 0; i <= nextBox.length; i++) {
      if (i === id) {
        nextBox[i] = [event.target.value, nextBox[i][1], nextBox[i][2]];
      }
    }
    setBoxvalue(nextBox);
  }

  function handleCatOption(event, index) {
    const tempTransaction = transaction.slice();
    tempTransaction[index] = [event.target.value, tempTransaction[index][1]];
    setTransaction(tempTransaction);
  }

  /* LEFTOVER FROM
  function remainder(event, index) {
    const newTrans = transaction.slice();
    const arr = newTrans[index][1].toString().slice();
    const filterArr = [];
    const boxstr = modifyNum(arr, filterArr);
    const str = boxstr.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    return "¥" + str;
  }

  */

  return (
    <div className="App">
      <h1> Budget thingy </h1>
      <div className="budget">
        <div className="category">
          {boxvalue.map((value, index) => (
            <CategoryName key={index} idval={index} categname={handleCatName} />
          ))}
        </div>
        <div className="categoryamount" id="inputamount">
          {boxvalue.map((value, index) => (
            <CategoryAmount
              key={index}
              idval={index}
              parentCallback={handleInput}
            />
          ))}
        </div>
        <div className="amount-box" id="amountdiv">
          {boxvalue.map((value, index) => (
            <AmountBox
              key={index}
              idval={index}
              Numvalue={value[1]}
              Spent={value[2]}
            />
          ))}
        </div>
        <div className="newbutton">
          <NewBox
            title="New Category"
            handleClick={() => setBoxvalue([...boxvalue, ["", 0]])}
          />
        </div>
        <div className="total-amount" id="total">
          <p>Total: ¥{total.toLocaleString()}</p>
        </div>
      </div>
      <div className="transactions">
        <div className="column">
          {transaction.map((event, index) => (
            <input placeholder={index} className="trans-name" key={index} />
          ))}
        </div>
        <div className="column">
          {transaction.map((event, index) => (
            <input
              placeholder="Date"
              className="date"
              type="date"
              key={index}
            />
          ))}
        </div>
        <div className="column">
          {transaction.map((eventData, index) => (
            <TransCat
              categories={boxvalue}
              key={index}
              change={(extra) => handleCatOption(extra, index)}
            />
          ))}
        </div>
        <div className="column">
          {transaction.map((eventData, index) => (
            <input
              placeholder="Expenditure"
              className="expend"
              id="out"
              key={index}
              onChange={(event) => handleInput(event, index)}
            />
          ))}
        </div>
        <div className="column">
          {transaction.map((eventData, index) => (
            <input
              placeholder="Income"
              className="income"
              id="in"
              key={index}
              onChange={(event) => handleInput(event, index)}
            />
          ))}
        </div>

        <div className="newbutton">
          <button
            className="newrow"
            onClick={setTransaction([["", 0, 0], ...transaction])}
          >
            New Row
          </button>
        </div>
      </div>
    </div>
  );
}

  • which re-rendering component, or where do you think is causing this error?!

happy coding :slight_smile:

React 18 has automatic batching (read the bottom-most point).


This handler is invoked immediately, it should be wrapped in a callback

<button
  className="newrow"
  onClick={setTransaction([["", 0, 0], ...transaction])}
>
  New Row
</button>
1 Like

This worked but not sure why the handler was being invoked immediately when I put it in an onClick? How is that looping a render?

It doesn’t matter that it is inside an onClick you are still calling the function. It has to be a callback. Event handlers work by passing them a callback (function definition) to be run when the event is triggered.

The function is updating the state and updating the state causes the component to re-render which runs all the code again.

The component renders, the function runs, the function updates the state, and the state update causes the component to re-render, so the function runs again, and the state updates again, over and over again.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.