Skip to content
Advertisement

Not sure how to stop timer from resetting upon changing nav tabs

Codesandbox I have an app that records user screen time on pages within an app. However, when I click between nav bar tabs(‘About’ and ‘Time’), the timer resets. enter image description here

I do not want it to reset. I want it to pause once I change the nav bar tab and resume when I return to the same tab. I tried using setInterval instead of setTimeout but there is no change.

About.js

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

  const About = () => {
    const [time, setTime] = useState({
      seconds: 0,
      minutes: 0,
      hours: 0,
    });

    useEffect(() => {
      let isCancelled = false;

      const advanceTime = () => {
        setInterval(() => {
          let nSeconds = time.seconds;
          let nMinutes = time.minutes;
          let nHours = time.hours;

          nSeconds++;

          if (nSeconds > 59) {
            nMinutes++;
            nSeconds = 0;
          }
          if (nMinutes > 59) {
            nHours++;
            nMinutes = 0;
          }
          if (nHours > 24) {
            nHours = 0;
          }

          !isCancelled && setTime({ seconds: nSeconds, minutes: nMinutes, hours: nHours });
        }, 1000);
      };
      advanceTime();

      return () => {
        //final time:
        console.log(time);
        isCancelled = true;
      };
    }, [time]);

    return (
      <div>
        <p>
          {`
            ${time.hours < 10 ? '0' + time.hours : time.hours} :
            ${time.minutes < 10 ? '0' + time.minutes : time.minutes} :
            ${time.seconds < 10 ? '0' + time.seconds : time.seconds}
          `}
        </p>
      </div>
    );
  }

export default About; 

Time.js

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

  const Time = () => {
    const [time, setTime] = useState({
      seconds: 0,
      minutes: 0,
      hours: 0,
    });

    useEffect(() => {
      let isCancelled = false;

      const advanceTime = () => {
        setInterval(() => {
          let nSeconds = time.seconds;
          let nMinutes = time.minutes;
          let nHours = time.hours;

          nSeconds++;

          if (nSeconds > 59) {
            nMinutes++;
            nSeconds = 0;
          }
          if (nMinutes > 59) {
            nHours++;
            nMinutes = 0;
          }
          if (nHours > 24) {
            nHours = 0;
          }

          !isCancelled && setTime({ seconds: nSeconds, minutes: nMinutes, hours: nHours });
        }, 1000);
      };
      advanceTime();

      return () => {
        //final time:
        console.log(time);
        isCancelled = true;
      };
    }, [time]);

    return (
      <div>
        <p>
          {`
            ${time.hours < 10 ? '0' + time.hours : time.hours} :
            ${time.minutes < 10 ? '0' + time.minutes : time.minutes} :
            ${time.seconds < 10 ? '0' + time.seconds : time.seconds}
          `}
        </p>
      </div>
    );
  }

export default Time;

App.js

import React from 'react';
import logo from './logo.svg';
import './App.css';
import Nav from './component/Nav';
import About from './component/About';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import Time from './component/Time';

function App() {
  return (
   <Router>
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div>
          <Nav />
           <Switch>
            <Route path = "/about" exact component = {About}/>
            <Route path = "/time" exact component = {Time}/>
          </Switch>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
   </Router>
  );
}


export default App; 

Advertisement

Answer

This is just the gist of what you’ll need to do. Components will be re-rendered when you navigate to another route and back. Because of this, their state will be no longer exist.

To maintain state, you could “lift” your state to a parent component. This means instead of your About and Time component managing their own state, App would manage both their states.

This would look something like this:

function App() {
    const [time, setTime] = useState({
        seconds: 0,
        minutes: 0,
        hours: 0,
    });

    const [isAboutTimerOn, setIsAboutTimerOn] = useState(false)

    const advanceTime = () => {
        setIsAboutTimerOn(true)
        setInterval(() => {
            let nSeconds = time.seconds;
            let nMinutes = time.minutes;
            let nHours = time.hours;

            nSeconds++;

            if (nSeconds > 59) {
                nMinutes++;
                nSeconds = 0;
            }
            if (nMinutes > 59) {
                nHours++;
                nMinutes = 0;
            }
            if (nHours > 24) {
                nHours = 0;
            }

            isAboutTimerOn && setTime({ seconds: nSeconds, minutes: nMinutes, hours: nHours });
        }, 1000);
    };



    const startTime = () => {
        advanceTime()
    }

    const stopTime = () => {
        setIsAboutTimerOn(false)
    }

    return (
        <Router>
            <div className="App">
                <Nav />
                <Switch>
                    <Route path="/about" exact component={() => <About startTime={startTime} stopTime={stopTime} time={time}/>}/> // pass props
                </Switch>
            </div>
        </Router>
    );
}

The logic for your use case may not be correct but the question is related to state management

Since you are managing two different states, your code will be more complex. You should be able to make some of you logic reusable though.

Then your child components would look something like:

const About = ({ time, startTime, stopTime }) => {

    useEffect(() => {
        startTime()

        return () => {
            stopTime()
        }

    }, [startTime, stopTime])


    return (
        <div>
            <p>
                {`
            ${time.hours < 10 ? '0' + time.hours : time.hours} :
            ${time.minutes < 10 ? '0' + time.minutes : time.minutes} :
            ${time.seconds < 10 ? '0' + time.seconds : time.seconds}
          `}
            </p>
        </div>
    );
}
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement