title: React - How do I start a timer, then navigate a different route, then come back and have the timer still in sync with its original countdown?
body: <p>The title really says it all. I've seen some questions about how to keep a component from un-mounting, but I'm not sure that's exactly what I need (or at least not all that I need).</p>
  <p>Right now I have the timer updating from a background thread so that when the browser tab goes into the background it doesn't fall asleep. But I also want it to continue counting when I go to a different screen in the app. (I'm making a time tracking app).</p>
  <p>Can anyone tell me how to approach this?</p>
  <p>Thanks</p>
  <p>My full clock.js - unfortunately it's nested in some other components right now and would be a chore to lift the logic to the top level component.</p>
  <p>The clock works in conjunction with a background task, which I've posted right below it.</p>
  <pre><code>import React, { useEffect, useState, useRef, useContext } from 'react'
  import './Clock.css'
  import useSound from 'use-sound';
  import bark from '../../Sounds/bong.wav';
  import tweet from '../../Sounds/bong.wav';
  import gong from '../../Sounds/opening_gong.wav'
  import SkipNext from '@material-ui/icons/SkipNextOutlined';
  import Stop from '@material-ui/icons/StopOutlined';
  import PausePresentation from '@material-ui/icons/PauseOutlined';
  import PlayCircleOutline from '@material-ui/icons/PlayArrowOutlined';
  import MainContext from '../../MainContext'
  import myWorker from '../../test.worker';
  
  
  
  function Clock(props) {
  
    const context = useContext(MainContext)
    let breakPrefs = context.prefs
  
    const [timer, setTimer] = useState({
      onBreak: false,
      firstPageLoad: true,
      isPaused: true,
      time: 0,
      timeRemaining: 0,
      cycle: 0,
      skipped: false
    })
  
    const [playBark] = useSound(bark,
      { volume: 0.65 }
    );
  
    const [playTweet] = useSound(tweet,
      { volume: 0.20 }
    );
  
    const [playGong] = useSound(gong,
      { volume: 0.20 }
    );
  
  
    
  
  
  
    //sets up a worker thread to keep the clock running accurately when browser is in background
    const worker = useRef()
    useEffect(() =&gt; {
      worker.current = new myWorker()
      return () =&gt; {
        worker.current.terminate();
      }
    }, [])
  
    //updates time remaining in state from the worker thread every second
    useEffect(() =&gt; {
      const eventHander = e =&gt; {
        if(e.data === true){
          //here is where the sound should play
         
        }else{
          setTimer((timer) =&gt; ({
            ...timer,
            timeRemaining: e.data
          }))
        }
  
      }
  
      worker.current.addEventListener('message', eventHander)
      return () =&gt; {
        worker.current.removeEventListener('message', eventHander)
      }
    }, [])
  
  
  
    //stops the countdown from resetting during certain UI events 
    let allowCountdownRestart = false
    useEffect(() =&gt; {
      if (allowCountdownRestart) {
        allowCountdownRestart = false
      } else {
        allowCountdownRestart = true
      }
    }, [props, allowCountdownRestart])
  
    useEffect(() =&gt; {
      allowCountdownRestart = false
    }, [breakPrefs, props.noClockStop, context.handleAddProject, context.currentProject]);
  
  
  
    //resets the timer when the user selects a new cycle
    //couldn't pass in [props.cycle] for this because of an issue when user selects the same cycle twice
    useEffect(() =&gt; {
  
      if (allowCountdownRestart) {
        worker.current.postMessage({ message: &quot;start&quot;, &quot;time&quot;: props.cycle * 60 })
  
        setTimer((timer) =&gt; ({
          ...timer,
          time: props.cycle * 60,
          timeRemaining: props.cycle * 60,
          cycle: props.cycle,
          onBreak: false
        }));
  
      }
    }, [props])
  
   
  
    //starts the timer after it's reset 
    useEffect(() =&gt; {
      if (timer.time &gt; 0) {
        worker.current.postMessage({ message: &quot;start&quot;, &quot;time&quot;: timer.time })
      }
    }, [timer.time])
  
    //listens for when take break is pressed
    useEffect(() =&gt; {
      if(props.takeBreak !== 0){
        setTimer((timer) =&gt; ({
          ...timer,
          onBreak: true
        }))
      }
    
  
    }, [props.takeBreak])
  
    //handles the automatic switch to a break after a regular cycle 
    useEffect(() =&gt; {
  
  
      if (timer.time === 0 &amp;&amp; !timer.firstPageLoad) {
        setTimeout(function () {
          if (timer.onBreak) {
            timer.onBreak = false
          } else {
  
            
            // const breakDuration = breakPrefs[&quot;break_duration&quot;] * 60
            // if (breakDuration !== 0) {
            //   worker.current.postMessage({ message: &quot;start&quot;, &quot;time&quot;: breakDuration })
            //   setTimer((timer) =&gt; ({
            //     ...timer,
            //     onBreak: true,
            //     time: breakDuration,
            //     timeRemaining: breakDuration
            //   }));
            // }
  
            if (!timer.skipped) {
              props.updateDBWithTask(timer.cycle)
              props.subtractFromTimeUntilBreak(timer.cycle, false) 
  
            }
            setTimer((timer) =&gt; ({
              ...timer,
              skipped: false
            }));
          }
        }, 1000);
  
      } else {
        if (timer.time === timer.timeRemaining) {
          timer.firstPageLoad = false
          handleResume()
        }
      }
    }, [timer.time, timer.time === timer.timeRemaining])
  
  
    //determines which sound to play, and resets the timer to its original state at the end of a cycle.
    useEffect(() =&gt; {
  
      if (timer.timeRemaining === 0) {
  
        if(!timer.onBreak){
          playTweet()
        }
  
        setTimer((timer) =&gt; ({
          ...timer,
          time: 0,
          isPaused: true
        }));
  
      }
    }, [timer.timeRemaining])
  
  
  
    //listens for pause/unpause and updates timer accordingly
    useEffect(() =&gt; {
      if (timer.isPaused) {
        worker.current.postMessage({ message: &quot;pause&quot;, &quot;time&quot;: timer.timeRemaining })
  
  
      } else {
        worker.current.postMessage({ message: &quot;start&quot;, &quot;time&quot;: timer.timeRemaining })
      }
    }, [timer.isPaused])
  
  
  
    const handlePause = e =&gt; {
      setTimer({ ...timer, isPaused: true })
    }
  
   
  
    const handleResume = e =&gt; {
      if (timer.time !== 0) {
        setTimer({
          ...timer,
          isPaused: false
        })
      }
    }
  
    const handleSkip = () =&gt; {
  
      const elapsedMinutes = Math.floor((timer.time - timer.timeRemaining) / 60)
      const remainingSeconds = (timer.time - timer.timeRemaining) - elapsedMinutes * 60;
      const roundedMinutes = remainingSeconds &gt; 30 ? elapsedMinutes + 1 : elapsedMinutes
      
      if(roundedMinutes &gt; 0 &amp;&amp; !timer.onBreak){
        props.updateDBWithTask(roundedMinutes)
        props.subtractFromTimeUntilBreak(roundedMinutes, true)
      }
  
      setTimer({ ...timer, skipped: true, timeRemaining: 0 })
      worker.current.postMessage({ message: &quot;stop&quot;, &quot;time&quot;: 0 })
    }
  
    const handleStop = () =&gt; {
      setTimer({ ...timer, onBreak: true, cycle: 0, timeRemaining: 0 })
      worker.current.postMessage({ message: &quot;stop&quot;, &quot;time&quot;: 0 })
    }
  
  
  
  
    const timeFormat = (duration) =&gt; {
  
      if (duration &gt; 0) {
        var hrs = ~~(duration / 3600);
        var mins = ~~((duration % 3600) / 60);
        var secs = ~~duration % 60;
        var ret = &quot;&quot;;
        if (hrs &gt; 0) {
          ret += &quot;&quot; + hrs + &quot;:&quot; + (mins &lt; 10 ? &quot;0&quot; : &quot;&quot;);
        }
        ret += &quot;&quot; + mins + &quot;:&quot; + (secs &lt; 10 ? &quot;0&quot; : &quot;&quot;);
        ret += &quot;&quot; + secs;
        return ret;
      } else {
        return &quot;00:00&quot;
      }
    }
  
  
    return &lt;&gt;
  
      &lt;div className=&quot;floatLeft&quot;&gt;
        &lt;div id=&quot;timer&quot;&gt;
  
          {timer.onBreak ?
            &lt;div&gt;&lt;h2 className=&quot;display-timer-header&quot;&gt;On Break &lt;/h2&gt; &lt;h2 className=&quot;display-timer&quot;&gt;{timeFormat(timer.timeRemaining)}&lt;/h2&gt;&lt;/div&gt;
            : &lt;div&gt;&lt;h3 className=&quot;display-timer-header&quot;&gt; Time Left &lt;/h3&gt; &lt;h3 ref={context.timerRef} className=&quot;display-timer&quot;&gt;{timeFormat(timer.timeRemaining)}&lt;/h3&gt;&lt;/div&gt;}
  
  
          &lt;div className=&quot;toolbar-container&quot;&gt;
            &lt;div className={`toolbar-icons ${props.taskBarOpen ? &quot;taskbar-open&quot; : &quot;&quot;}`}&gt;
              &lt;i className=&quot;tooltip&quot;&gt;&lt;Stop className=&quot;toolbar-icon&quot; onClick={handleStop}&gt;&lt;/Stop&gt;
                &lt;span className=&quot;tooltiptext&quot;&gt;Stop/Cancel&lt;/span&gt;&lt;/i&gt;
              {!timer.isPaused ?
                &lt;i className=&quot;tooltip pause&quot;&gt;&lt;PausePresentation className=&quot;toolbar-icon&quot; onClick={handlePause}&gt;&lt;/PausePresentation&gt;
                  &lt;span className=&quot;tooltiptext pause-tooltip&quot;&gt;Pause&lt;/span&gt;&lt;/i&gt;
                :
                &lt;i className=&quot;tooltip pause&quot;&gt;&lt;PlayCircleOutline className=&quot;toolbar-icon&quot; onClick={handleResume}&gt;&lt;/PlayCircleOutline&gt;
                  &lt;span className=&quot;tooltiptext&quot;&gt;Resume&lt;/span&gt;&lt;/i&gt;
              }
              &lt;i className=&quot;tooltip&quot;&gt;&lt;SkipNext className=&quot;toolbar-icon&quot; onClick={handleSkip} &gt;&lt;/SkipNext&gt;
                &lt;span className=&quot;tooltiptext&quot;&gt;Finish Early&lt;/span&gt;&lt;/i&gt;
  
            &lt;/div&gt;
          &lt;/div&gt;
  
        &lt;/div&gt;
      &lt;/div&gt;
  
    &lt;/&gt;
  }
  
  
  
  
  export default Clock;
  
  </code></pre>
  <p>backgroundtask.js</p>
  <pre><code>
  let myInterval;
  /* eslint-disable-next-line no-restricted-globals */
  self.onmessage = function(evt) {
      clearInterval(myInterval);
  
        if(evt.data.message === 'pause' || evt.data.message === 'stop' || evt.data.message === 'skip'){
          postMessage(evt.data.time)
        }
  
      if (evt.data.message == &quot;start&quot; || evt.data.message == &quot;break&quot;) {
          var i = evt.data.time;
          myInterval = setInterval(function() {
              i--;
              postMessage(i);
          }, 1000);
          
      } 
    
  };
  </code></pre>