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.
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> ); }