Home > Mobile >  React.js have timer on component start only once, not everytime upon rerender
React.js have timer on component start only once, not everytime upon rerender

Time:11-18

I tried setting a timer to a function I want to be called every 2 seconds:

      // start timer
      if(!timerStarted){
        tid = setInterval(ReloadMessage, 2000);
        timerStarted = true
      }

But I want this timer instance to only be ran once (hence the "!timerStarted")

Unfortunately, this gets ignored when the component rerenders from the state change. I tried ending the timer but I found no way to know in advance when the state changes.

So I tried:

React.useEffect(()=>{
  (async () => {
              // start timer
              if(!timerStarted){
                tid = setInterval(ReloadMessage, 2000);
                timerStarted = true
              }
  })()
},[])  

Thinking this would make the effect be called only once upon component load, but this ended up not calling the timer at all (Maybe because I also have a second effect with dependencies here?)

Anyway:

How do I make sure this timer is set off once and only once, no matter what the user does?

Thank you

CodePudding user response:

Using an empty dependencies array for your effect, will ensure that it only runs once. With that in mind, it's kind of irrelevant to track that a timerStarted.

The usage of this flag (provided it's a variable scoped to the component) even indicates that it actually should be a dependency, which your linter, if you have one, should notify you of. Though as stated above you don't need it, and it would only make things more complicated.

Also the async IIEF is not needed as you don't await anything.

So, all in all, this should be enough:

React.useEffect(()=>{
  const tid = setInterval(ReloadMessage, 2000);

  return () => {
    clearInterval(tid);
  };
},[]);

As per the comments, here's a simple demo of how you can use a ref, to get access to some dependency that you absolutely do not want to list as a dependency. Use this sparingly and only with good consideration, because it often hints at a problem that started somewhere else (often a design problem):

import { useEffect, useRef, useState } from 'react';

const Tmp = () => {
  const [counter, setCounter] = useState(0);
  const counterRef = useRef(counter);

  useEffect(() => {
    counterRef.current = counter;
  }, [counter]);

  useEffect(() => {
    const t = setInterval(() => {
      console.log('Invalid', counter);          // always *lags behind* because of *closures* and
                                                // will trigger a linter error, as it should actually be a dependency
      console.log('Valid', counterRef.current); // current counter
    }, 2000);

    return () => {
      clearInterval(t);
    };
  }, []);

  return (
    <div>
      <div>
        <button onClick={() => setCounter(current => current - 1)}>-</button>
        &nbsp;{counter}&nbsp;
        <button onClick={() => setCounter(current => current   1)}> </button>
      </div>
    </div>
  );
};

export default Tmp;
  • Related