Search code examples
reactjstimersetintervalreact-props

How to write a timer interval that is dependent on a prop in react?


I am creating a simple popup window component for my website and I want to periodically gather any new or incoming messages about every second or so for live messaging functionality. I am currently using react for my project.

My main problem is, I only want to gather messages when a prop called "active" is set to True, and I can't figure out how to do so.

Here is a mockup of my web page that could hopefully help. It is of a mockup of the webpage and has three entries, one with an open messaging window.

I have multiple entries that can have their messaging window open, but I've passed other props down so that only one messaging window can be open at a time. So it is possible to have an interval / timer thing in one of the component's ancestors instead, but I wanted to make this popup window more self-sufficient with its own call to the API.

I've tried using a setInterval function that initializes in a useEffect function inside the messaging window component:

    let my_interval = useRef() // ???? Don't think this is the solution

    const timerHandler = useCallback(() => {
        // Some call to my RestAPI here.
        console.log("Timer called")
    }, active)

    // If the "active" prop turns true, the interval should be running. Else, clear the interval if it exists.
    // Here, the trouble is the interval handle "my_interval" is changing so I can't clear the original interval.
    useEffect(() => {
        if (active) my_interval = setInterval(timerHandler, 1000);
        else if (!active) clearInterval(my_interval)
    }, [active])

However, as the comment points out, I can't actually clear the interval because I can't find a way to make the interval handle my_interval stable enough (useRef doesn't work)

I also tried using a timeout function with a recursive call that will keep calling itself as long as active is true, but I think I have a similar issue in that the function itself is changing and so the recursive call is kind of broken.

    // Only do action when active is set to true. This doesn't work, when active changes to false the timer still runs.
    const timeoutHandler = useCallback(() => { 
        if (active) console.log("Timer called")
        setTimeout(timeoutHandler, 1000);
    }, [active])

    // Set a timeout on render, regardless of "active"
    useEffect(() => {
        setTimeout(timeoutHandler, 1000);
    }, [])

Any help or different strategies would be much appreciated, I really want my messenger popup to be self-sufficient with its own call to my RestAPI but it almost seems impossible at this point.


Solution

  • useRef is an object, see docs of its API:

    const intervalRef = useRef();
    
    // handle on active state change
    useEffect(() => {
      if (active) {
        intervalRef.current = setInterval(() => {
          console.log("Timer called");
        }, 1000);
      } else {
        clearInterval(intervalRef.current);
      }
    }, [active]);
    
    // handle unmount
    useEffect(() => {
      const intervalId = intervalRef.current;
      return () => {
        clearInterval(intervalId);
      };
    }, []);