Home > Blockchain >  How to fix this recursive function
How to fix this recursive function

Time:01-03

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.

CodePudding user response:

Your function does not work because it will not take into consideration any changes that happen to the state. Same thing for setTimeout().

This is because when setTimeout() or the recursive func() are scheduled, they are using the values of sum and array at the time they were scheduled.

If you want to store a value that is capable of being updated during the execution, you should use the useRef hook. More about it here.

PS: When the value of useRef changes, it does not cause a re-render like useState. It also won't trigger useEffect if put in its dependency array.

First, start by declaring two refs like so:

// Your states
const [array, setArray] = useState([1, 2, 3]);
const [sum, setSum] = useState(0);
// The refs
const arrayRef = useRef([1, 2, 3]);
const sumRef = useRef(0);

Then, whenever array and sum changes, you should update the values of these refs:

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

useEffect(() => {
   sumRef.current = sum;
}, [sum]);

Finally, you make the recursive function work with the refs instead of the states:

const func = () => {
if (sumRef.current >= 10) return;

setTimeout(() => {
  getRandNum();
  console.log(arrayRef.current, sumRef.current);
  func();
  }, 500);
};

You can see the code in action in this codesandbox

Hope this helps :)

CodePudding user response:

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>

  • Related