Search code examples
javascriptreactjssetintervalreact-typescript

setInterval in a react / typescript app : including state dependencies causes it to stop running


Something that I am struggling to understand in a react app is the correct use of setInterval, when i want to have an option that just exists early (but keeps the interval running) where that option is some piece of state within the app.

I have the following useEffect:

useEffect(() => {
  if (wallet.publicKey && intervalref.current === null) {
    intervalref.current = window.setInterval(set_balance, 1000);
  } else {
    if (intervalref.current !== null) {
      window.clearInterval(intervalref.current);
      intervalref.current = null;
    }
  }
  return () => {
    if (intervalref.current !== null) {
      window.clearInterval(intervalref.current);
    }
  };
}, [set_balance, wallet]);

Here wallet comes from a 3rd party library and is used to get the users balance.

set_balance is then not very complex:

const set_balance = useCallback(async () => {
  console.log("in increase", balance, check_balance);

  if (wallet === null) return;

  if (check_balance === false) return;

  let current_balance = await get_current_balance(wallet);

  if (current_balance !== balance) {
    setCheckBalance(false);
  }

  setBalance(current_balance);
}, [wallet, balance, check_balance]);

The idea then is that some other function within this component is going to do setCheckBalance(true), and then the above will run calling get_current_balance() until a change is detected, at which point the function will just start exiting early (but still runs).

Unfortunately this doesn't work. The interval only runs a couple of times and then stops. However, if I remove the dependencies on balance and check_balance, and replace them with global variables rather than state, it works.

Unfortunately that is a very limited use-case solution, as I want other components in other files to be able to setCheckBalance, whereas they can't set exported global variables.

What is the 'react' way of doing this?


Solution

  • In the return function of your useEffect, you do not set

    intervalref.current = null;
    

    So when the cleanup function of your useEffect is triggered, you stop your interval, but with all the conditions you have, you never start a new one.

    Unralated to your problem, but check_balance would better be a useRef than a state, so your useCallback and useEffect are not triggered again