javascriptreactjssassreact-hook-form

Blank page on the browser in my ReactJS app using React Hook Form


The browser shows me blank page and I see no error in the VS Code and terminal, but in the console, I see:

"Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. at renderWithHooksAgain (react-dom-client.development.js:5614:17) at renderWithHooks (react-dom-client.development.js:5532:21) at updateFunctionComponent (react-dom-client.development.js:8897:19) at beginWork (react-dom-client.development.js:10522:18) at runWithFiberInDEV (react-dom-client.development.js:1519:30) at performUnitOfWork (react-dom-client.development.js:15132:22) at workLoopSync (react-dom-client.development.js:14956:41) at renderRootSync (react-dom-client.development.js:14936:11) at performWorkOnRoot (react-dom-client.development.js:14462:44) at performWorkOnRootViaSchedulerTask (react-dom-client.development.js:16216:7)"

and a warning:

"hook.js:608 An error occurred in the <App> component.

Consider adding an error boundary to your tree to customize error handling behavior.

Visit https://react.dev/link/error-boundaries to learn more about error boundaries."


This is my code in the app.jsx component:

const tipPercents = [5, 10, 15, 25, 50];

export default function App() {
  const { register, handleSubmit, setValue, watch } = useForm();
  const [tipPercent, setTipPercent] = useState(null);

  const customTip = parseFloat(watch('customTip')) || null;
  const bill = parseFloat(watch('bill')) || 0;
  const people = parseFloat(watch('people')) || 0;

  const tip = customTip || tipPercent || 0;
  const tipAmount = people ? ((bill * tip) / 100) / people : 0;
  const total = people ? (bill + (bill * tip) / 100) / people : 0;

  const handleTipClick = (value) => {
    setTipPercent(value);
    setValue('customTip', '');
    setValue('tipPercent', value)
  };

  const reset = () => {
    setTipPercent(null);
    setValue('bill', '');
    setValue('people', '');
    setValue('customTip', '');
  };

  return (
    <div>
      <div>
        <form onSubmit={handleSubmit(() => {})}>

          <div className='left'>
            <div>
              <label>Bill</label>
              <div>
                <img src={dollar} alt="bill" />
                <input type="number" placeholder='0' {...register('bill')} />
              </div>
            </div>
            <div>
              <p>Select Tip %</p>
              <div>
                {/* tip button components go here */}
                {tipPercents.map((percent) => (
                  <TipButton
                    key={percent}
                    value={percent}
                    isActive={tipPercent === percent}
                    onClick={handleTipClick}
                   />
                ))}
              </div>
              <input
                type="number" 
                placeholder='Custom' 
                {...register('customTip')} 
                onFocus={setTipPercent(null)} 
              />
            </div>
            <div>
              <label>Number of People</label>
              <div>
                <img src={person} alt="person" />
                <input type="number" placeholder='0' {...register('people')} />
              </div>
            </div>
          </div>

          <div className='right'>
            <div>
              {/* result components go here */}
              <ResultRow title={"Tip Amount"} amount={tipAmount.toFixed(2)} />
              <ResultRow title={"Total"} amount={total.toFixed(2)} />
            </div>
            <button type='button' onClick={reset}>RESET</button>
          </div>

        </form>
      </div>
    </div>
  )
}

this is tipButton.jsx component:

export default function TipButton ({ value, onClick, isActive }) {
    return (
        <button
            type="button"
            onClick={() => onClick(value)}
            className={`tip-button ${isActive === value ? 'active-button' : ''}`}
        >
            {value}%
        </button>
    )
}

ResultRow.jsx:

export default function ResultRow ({ title, amount }) {
    return (
        <div>
            <div>
                <p>{title}</p>
                <p>/ person</p>
            </div>
            <h3>${amount}</h3>
        </div>
    )
}

I asked chatgpt and it said the problem is related to something like setValue or onClick function for TipButton component. I tried the solution it gave me but it didn't work.


Solution

  • onFocus={setTipPercent(null)} 
    

    This triggers a state update during rendering, which forces React to re-render, which then calls setTipPercent(null) again, creating an infinite loop.

    Try changing it to use an arrow function to delay execution until focus:

    onFocus={() => setTipPercent(null)}