1

I'm trying to create a simple recursive function that adds random numbers to an array until the sum of that array is 10.

function App() {
    const [array, setArray] = useState([1, 2, 3]);
    const [sum, setSum] = useState(0);

    const getRandNum = () => {
        const num = Math.floor(Math.random() * 5);
        setArray((prev) => [...prev, num]);
    };

    useEffect(() => {
        setSum(() => {
            return array.reduce(
                (acumulator, currentValue) => acumulator + currentValue,
                0
            );
        });
    }, [array]);

    const func = () => {
        if (sum >= 10) return;

        setTimeout(() => {
            getRandNum();
            func();
        }, 500);
    };
    return (
        <>
            <div className="App">
                {array.map((num) => (
                    <p>{num}</p>
                ))}
            </div>
            <button onClick={() => func()}>click</button>
            <p>sum: {sum}</p>
        </>
    );
}

This recursive function func() does not stop calling itself. It keeps adding numbers even though sum is greater than 10.

I know what's the problem but I don't know how to fix it. If I add console.log into func like this:

const func = () => {
    if (sum >= 10) return;

    setTimeout(() => {
        getRandNum();
        console.log(array, sum);
        func();
    }, 500);
};

It always logs the same numbers: [1,2,3] 6. So even though array and sum are changing, function keeps calling itself with same values and it does not stop.

Janez04
  • 13
  • 6

1 Answers1

0

You need to be able to reference the new values that are put in state. Doing just getRandNum(); means that you don't have access to them elsewhere in that block; you might be triggering a re-render, but that func function still closes over the value array had at the moment func was first invoked.

A good way to do this would be to add another state - a boolean flag that indicates whether you're in the process of adding values or not. Check that state on every render and perform the psuedo-recursive asynchronous update if needed.

Also, the sum state is superfluous, because it depends entirely on another state, array. (Don't duplicate state!) Use useMemo instead to indicate the dependency and calculate it synchronously.

const { useState, useEffect, useMemo } = React;
function App() {
    const [array, setArray] = useState([1, 2, 3]);
    const sum = useMemo(() => array.reduce((a, b) => a + b, 0), [array]);
    const [funcRunning, setFuncRunning] = useState(false);
    const getRandNum = () => {
        const num = Math.floor(Math.random() * 5);
        setArray((prev) => [...prev, num]);
    };
    // Run on every render:
    useEffect(() => {
        if (funcRunning) {
            if (sum >= 10) {
                setFuncRunning(false);
                return;
            }
            const timeoutId = setTimeout(getRandNum, 500);
            return () => clearTimeout(timeoutId);
        }
    });
    return (
        <div>
            <div className="App">
                {array.map((num, i) => (
                    <p key={i}>{num}</p>
                ))}
            </div>
            <button onClick={() => setFuncRunning(true)}>click</button>
            <p>sum: {sum}</p>
        </div>
    );
}

ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320