1

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:

   //my Functional component useEffect

   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?)

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

innom
  • 770
  • 4
  • 19
  • 1
    Please share the code of your component as well, so we can more easily understand your problem ;) – Batajus Nov 18 '21 at 09:58

1 Answers1

2

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;
Yoshi
  • 54,081
  • 14
  • 89
  • 103
  • THank you, this does work, but another weired thing appeared: If I want to access my props, the varibable always holds 0 in this effect. The effect I use above this one, which also referes to the props but has dependencies, never holds a 0. Is there something I need to do with this effect, so that I have access to the props? – innom Nov 18 '21 at 10:15
  • @innom I think you'll need to revisit the docs with regards to when you need a dependency and when not. In short, if you want to access anything outside of the effect closure, but still ensure that it corresponds to the current render, you need to list it as a dependency (this is why the effect with the probs (as you describe it) works, but the other does not). Listing it as a dependency though, makes the effect quite literally *depend* on it, so when one changes, the effect runs again. – Yoshi Nov 18 '21 at 10:23
  • Ultimately you'll need a thorough understanding of [how javascript closures work](https://stackoverflow.com/q/111102) to understand the details behind this. This isn't bound to react, but to javascript in general. – Yoshi Nov 18 '21 at 10:23
  • OK so but from what Iknow from react is that when an effect has dependencies it will rerun as soon as the dependecies change. I do not want that to happen clearly, or I will get many instances of my timer running all at once... – innom Nov 18 '21 at 10:26
  • Though, I would urge you to find a better strategy (which I cannot really provide without seeing more of your code), a simple solution would be to use `useRef` to make your dependencies available without listing them as dependencies (I'll include an example in the answer shortly). Personally I will always try to avoid this construct as long as possible though. – Yoshi Nov 18 '21 at 10:29
  • Thank you. It is probably a quick fix, but itll do for the clients budget as of now ;-). Thank you for your help so far. – innom Nov 18 '21 at 10:30
  • 1
    @innom I added an example, I hope it is somewhat self explanatory. – Yoshi Nov 18 '21 at 10:38