running setInterval in all pages react js

Viewed 37

i want to timer module in my web app when user click start it will running and not stop until user stop it. here is my code

useEffect(() => {
 let secondsInterval = () => {};
  if (clockRunning) {
    secondsInterval = setInterval(() => {
      localStorage.setItem("seconds", seconds+1);
      setseconds((prev) => prev + 1);
      if (seconds == 59) {
        localStorage.setItem("minutes", minutes+1);
        setminutes((prev) => prev + 1);
        setseconds(0);
        if (minutes == 59) {
          localStorage.setItem("hours", hours+1);
          setminutes(0);
          sethours((prev) => prev + 1);
        }
      }
    }, 1000);
  }
  return () => clearInterval(secondsInterval);
}, [seconds, clockRunning]);

if I remove clearInterval it will so much disturb in interval as it running anonymous. how can i run this setinterval even the component will unmount i:e change the route . i am using react-router-dom . thanks

1 Answers

You cant run it whilst the component is unmounted, as that violates basic react principles. The hooks and everything related to them are "owned" by that component and when thats unmounted, everything gets cleaned up by design. Not clearing the interval would be a bug as youd be calling setters that dont exist.

The answer is to hoist the state/hooks to a higher component like your application root, and then pass down the data via context or otherwise.

In a new file, TimerContext:

import { createContext } from 'react'

export const TimerContext = createContext({
    seconds: 0,
    minutes: 0,
    hours: 0,
    pauseClock: () => throw new Error('Must be called in context')
})


In a new file TimerProvider:

import { useState, useEffect, useCallback } from 'react'
import { TimerContext } from './TimerContext'

export const TimerProvider = ({children}) => {
    const [seconds, setSeconds] = useState(localStorage.getItem('seconds') ?? 0)
    const [minutes, setMinutes] = useState(localStorage.getItem('minutes') ?? 0)
    const [hours, setHours] = useState(localStorage.getItem('hours') ?? 0)
    const [clockRunning, setClockRunning] = useState(0)

    const pauseClock = useCallback((run) => setClockRunning(run), [])

    useEffect(() => {
     let secondsInterval;
      if (clockRunning) {
          secondsInterval = setInterval(() => {
          localStorage.setItem("seconds", seconds+1);
          setseconds((prev) => prev + 1);
          if (seconds == 59) {
            localStorage.setItem("minutes", minutes+1);
            setminutes((prev) => prev + 1);
            setseconds(0);
            if (minutes == 59) {
              localStorage.setItem("hours", hours+1);
              setminutes(0);
              sethours((prev) => prev + 1);
            }
          }
        }, 1000);
      }
      return () => secondsInterval && clearInterval(secondsInterval);
    }, [clockRunning]);

    return <TimerContext.Provider value={{ seconds, minutes, hours, 
 pauseClock}}> 
        {children}
       </TimerContext.Provider>

}

In one of your high level components like App or something:

import { TimerProvider } from './TimerProvider'

// existing stuff

const App = () => {
   // Existing stuff

   return <TimerProvider>
         {/* Your existing app contents here */}
       </TimerProvider>

}

Wherever you need to get the data

import { useContext } from 'react'
import { TimerContext } from './TimerContext'

const Consumer = () => {
   const { seconds, minutes, hours, pauseClock } = useContext(TimerContext)

}
Related