I am trying to make a countdown clock in my mobile view. But I can't seem to make the numbers work, my wrapper doesn't connects.
Hope somebody can see what I should do. I'm pretty new to Javascript and it all.
Code:
import React, { Component, Fragment, useState } from "react";
const MobileView = () => {
class wrapper extends Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
this.startInterval();
}
startInterval = () => {
const second = 1000;
const minute = second * 60;
const hour = minute * 60;
const day = hour * 24;
let x = setInterval(function () {
document.getElementById("days").innerText = "00";
}, second);
};
}
return (
<div className="wrapper">
<header className="Appheader">
<h1 id="head">Countdown</h1>
</header>
<div id="list">
<ul>
<li>
<span id="days"></span>Days
</li>
<li>
<span id="hours"></span>Hours
</li>
<li>
<span></span>Mins
</li>
<li>
<span></span>Seconds
</li>
</ul>
</div>
</div>
);
};
export default MobileView;
You seem to be mixing some concept:
You are are creating a class component, that you do not use, inside a functional component
You are trying to update the DOM directly instead of using the state
Here are the step you need to follow to make it work:
Choose between a class and a functional component
Setup your state accordingly
Start an interval that will update the state every X second
Clear the interval when the component is unmounted
Use your state value in the render function
Here is an working counter using a functional component, I will let you implement the logic for days, hours and minutes.
import React, { useEffect, useState } from "react";
// 1. Choose between a class and a functional component
export const MobileView = () => {
// 2. Setup your state accordingly
const [secondsElapsed, setSecondsElapsed] = useState(0);
const timer = () => {
setSecondsElapsed(secondsElapsed + 1);
};
useEffect(() => {
// 3. Start an interval that will update the state every X second
const interval = setInterval(timer, 1000);
// 4. Clear the interval when the component is unmounted
return () => clearInterval(interval);
});
return (
<div className="wrapper">
<header className="Appheader">
<h1 id="head">Countdown</h1>
</header>
<div id="list">
<ul>
<li>
<span></span>
{secondsElapsed} // 5. Use your state value in the render function
</li>
</ul>
</div>
</div>
);
};
also, I recommend to add useCallback and dependencies to the useEffect
const timer = useCallback(() => {
setSecondsElapsed(secondsElapsed => secondsElapsed + 1);
}, []);
useEffect(() => {
// 3. Start an interval that will update the state every X second
const interval = setInterval(timer, 1000);
// 4. Clear the interval when the component is unmounted
return () => clearInterval(interval);
}, [timer]);
Related
The gap variable should just give a difference once, but it gives a diff every second. I am not even updating its state. Even if i use settime in useEffect still the other variable that are changing in background are still effecting the page and are updating.
const ftime = dayjs('Dec 31,2021').unix();
const dateVar = dayjs().unix();
const gap = ftime - dateVar;
const [time, settime] = useState(dayjs().format('DD/MM/YYYY'));
useEffect(() => {
setInterval(() => settime(dayjs().second()), 1000);
}, []);
return (
<div className="App">
<div>{time}</div>
<div>{gap}</div>
</div>
)
useEffect(() => {
setInterval(() => settime(dayjs().second()), 1000)
}, [])
settime is invoked for every 1000ms. This will update the state (time) and trigger a rerender.
const ftime = dayjs('Dec 31,2021').unix();
const dateVar = dayjs().unix();
const gap = ftime - dateVar;
As these three variables are initialised at the top level of the function, the rerender will initialise them every time.
If you want to prevent this, you can move the variables outside the function component.
It is updating every second because you change time every second, which the component renders.
This rerendering will also cause the constants ftime and dateVar to reinitialise every second. If this is not intended, you need to put them outside the function or wrap them in a hook, such as useState or useMemo.
You can solve the rerendering issue by making the time a React component and placing the interval effect in that component. A child's rerendering normally doesn't trigger a rerender in the parent.
This is because in useEffect you are updating the state every second. When a state updates in a react function component the variables will be re-initialized and react doesn't track the values of the variables. It only keeps track on state and props.
Here you can make use of useRef hook since its is mutable and react does not re-initialize its value, which guarantees its value to be same during state update
import { useState, useEffect, useRef } from "react";
import dayjs from "dayjs";
import ReactDOM from "react-dom";
function App() {
const gap = useRef(dayjs("Dec 31,2021").unix() - dayjs().unix());
const [time, settime] = useState(dayjs().format("DD/MM/YYYY"));
useEffect(() => {
setInterval(() => settime(dayjs().second()), 1000);
}, []);
return (
<div className="App">
<div> {time}</div>
**<div>{gap.current}</div>**
</div>
);
}
I am working on my personal Portfolio using React and I want to add a statement on the landing page that displays my local time and timezone to recruiters, as seen below:
I have implemented this using the date-fns library but I am currently facing one issue. As time goes by, the time values on the screen stay constant and do not update themselves automatically. As we speak, it is currently 16:58 but the time still shows 16:47. I have to do a manual refresh for the updated time values to show. I want to implement this in such a way that the time will update every 60 seconds and show the current time always.
I wrote this Class component to implement the feature:
export class Time extends Component {
constructor() {
super();
var today = new Date();
var time = format(today, 'HH:mm')
this.state = {
currentTime: time
}
}
render() {
return (
<div className="time">
<p>
My Local Time is { this.state.currentTime } GMT +3
</p>
</div>
);
}
}
setInterval(Time, 60000);
What could I do to make this possible?
I tried using the solution provided Luke-shang-04 but I ran into issues with declaration of the intervalId variable. I therefore resorted to using React Hooks and converted this component into a Functional component.
The code below works:
export const Time = () => {
const [time, setTime] = useState(new Date());
useEffect(
() => {
const intervalId = setInterval(() => {
setTime(new Date());
}, 60000);
return () => {
clearInterval(intervalId)
}
}
)
return(
<div>
<p>{`My local time is ${format(time, 'HH:mm')} GMT+3`} </p>
</div>
)
}
Create a componentDidMount method, and place the setInterval in there. Make sure that the setInterval updates this.state.currentTime. It's also a good idea to clear the interval afterwards, so store the interval id and then call clearInterval in componentWillUnmount.
Something like this
export class Time extends Component {
intervalId
constructor() {
super()
var today = new Date()
var time = format(today, "HH:mm")
this.state = {
currentTime: time,
}
}
componentDidMount() {
var today = new Date()
var time = format(today, "HH:mm")
intervalId = setInterval(() =>
this.setState({
currentTime: time,
}),
60000
)
}
componentWillUnmount() {
clearInterval(intervalId) // Clear interval to prevent memory leaks
}
render() {
return (
<div className="time">
<p>My Local Time is {this.state.currentTime} GMT +3</p>
</div>
)
}
}
I would suggest that you read up on the React Docs, since these are the basics of React.
I'm trying to make a timer that will start (at 00:00) when the user loads the Home page, increment every second, and reset to 00:00 when they go to a different page.
Here's my newest code:
const [ timerCount, increaseTimer ] = useState(0);
function timerItself() {
function timerIncrement() {
var currentTimerCount = timerCount;
increaseTimer(currentTimerCount + 1);
};
setInterval(() => timerIncrement, 1000);
clearInterval(timerIncrement);
return;
}
function pageHome() {
var timerMinutes = JSON.stringify(Math.floor(timerItself / 60));
var timerSeconds = JSON.stringify(timerItself % 60);
timerItself();
return (
<div classname="App">
<header className="App-header">
<h1>Hello world!</h1>
<img src={logo} className="App-logo" alt="logo" />
<p>{timerMinutes.padStart(2, '0')}:{timerSeconds.padStart(2, '0')}</p>
<p>
<button onClick={(() => changePage('About'))}>
About
</button>
<button onClick={(() => changePage('Help'))}>
Help
</button>
</p>
</header>
</div>
);
}
Previously I had wrapped the setInterval() in a const like so:
const timer = setInterval(() => increaseTimer(timerCount + 1), 1000); and tried putting clearInterval() in the buttons' onClick functions thinking this would just reset the timer.
The output had weird behaviour: the timer would count up at irregular intervals, starting very slow from 00:03 instead of 00:00 and becoming very fast. The sequence of times shown was consistent but I couldn't recognise it as a mathematical sequence: 3, 7, 11, 29... 81... and reaching very high numbers within about 30 seconds. Couldn't figure out where in the code the numbers were coming from.
I did some more reading and came to the conclusion that timer was setting off more than one timer at the same time and somehow adding their outputs together. So I wrote timerItself() instead, with a clearInterval() in there which I assumed would also fire every second to clear the old timer and clean up for the new one. But now there's no counting going on at all, and the stringified output from the timer is "null:null".
I'm confused over where the clearInterval() should even go, as I think that's the main key to this problem. I'd really appreciate any help.
To create an interval that start when you render a component, you can use useEffect Hook, with your timerCount as a dependency. Because when you first render the component you want to start the counter, and as you increment the timer you want the interval to keep going.
import React, { useState, useEffect } from 'react';
export default function Home() {
const [timerCount, increaseTimer] = useState(0);
useEffect(() => {
let myTimerId;
myTimerId = setInterval(() => {
increaseTimer(timerCount + 1);
}, 1000);
return () => clearInterval(myTimerId);
}, [timerCount]);
return (
<div>
<h1>Timer {timerCount}</h1>
</div>
);
}
export function Other() {
return <div>This is the other Page</div>;
}
The arrow function returned by the useEffect Hook is a cleanup function that will clearTheInterval to prevent a memory leak.
And here is the App.js component
import React from 'react';
import { BrowserRouter as Router, Link, Route, Switch } from 'react-router-dom';
import Home, { Other } from './Home';
export default function App() {
return (
<>
<Router>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/other">Other</Link>
</li>
</ul>
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/other" component={Other} />
</Switch>
</Router>
</>
);
}
I used a library called react-router-dom to navigate between paths on your app, without making a new request.
Reference : https://reactrouter.com/web/guides/quick-start
And
useEffect : https://reactjs.org/docs/hooks-effect.html
// Also I suggest you to name the function that updates the state in such manner :
const [myExampleState, setMyExampleState] = useState('Example1');
setMyExampleState('Example2');
Hope I helped. ( Also check out the lifecycle to understand better how React works ;D )
Yeah setinterval doesn't play well with hooks. Even with the useEffect solution you'll lose time. If the interval is up to 3/4ths of a second, a rerender for any reason will destroy and recreate the timer back at zero again. Lots of rerenders, spaced out, effectively paralyze the timer.
This might be a good case to put the timer outside of the component. It's still in scope for the component without being controlled by it. Same for whatever function that's called on nav away.
Solved!
This code is working well for my purposes:
const [ timer, changeTimer ] = useState(0);
var currentTimerCount = timer;
useEffect(() => {
if (page !== 'Home')
changeTimer(0);
if (page == 'Home')
currentTimerCount = setInterval(() => {changeTimer(prevTimer => prevTimer + 1)}, 1000);
return () => clearInterval(currentTimerCount);
});
var timerMinutes = JSON.stringify(Math.floor(currentTimerCount / 60));
var timerSeconds = JSON.stringify(currentTimerCount % 60);
and then calling timerMinutes and timerSeconds as before.
Thanks to everyone who answered!
When I set a handler using OnClick it works fine and console.log always use the actual state. But when I use AddEventListener it works only once and then use the old state.
What the reason for than behaviour??
see the link
https://codesandbox.io/s/blazing-star-wf3c9?file=/src/App.js
P.S Using [count] as useEffect dependence fix it, but it just create new listener each time, don't think that the right way to do this
You have a closure issue.
In fact in each render your component going to create a new function showCount that you fixed the count.
For exemple:
In 1st render you create a function showCount that you fixed count = 1.
In your 2nd render you create a function showCount that you fixed count = 2.
In your 3 render you create a function showCount that you fixed count = 3.
The issue is that you are not changing the showcount when your count changes like this.
useEffect(() => {
let buttonShow = document.querySelector(".buttonShow");
buttonShow.addEventListener("click", showCount, true);
return () => {
buttonShow.removeEventListener("click", showCount, true);
};
}, [count]);
You can see the log count 2 because the reference of the count doesn't change when you use incremental.
I think the best solution is to you use a Component class:
import React from "react";
class CounterAddEvent extends React.PureComponent {
state = {
count: 1
};
componentDidMount() {
let buttonShow = document.querySelector(".buttonShow");
buttonShow.addEventListener("click", this.showCount, true);
}
componentWillUnmount() {
let buttonShow = document.querySelector(".buttonShow");
buttonShow.removeEventListener("click", this.showCount, true);
}
increaseCount = () => {
this.setState({ count: this.state.count + 1 });
};
showCount = () => {
console.log(this.state.count);
};
render() {
return (
<div>
<button onClick={this.increaseCount} className="input">
increase
</button>
<button>{this.state.count}</button>
<button className="buttonShow">Show console</button>
</div>
);
}
}
export default CounterAddEvent;
Bug came from setting "useState(1)" this will always reinitialize the value "count" to 1 before implementing "++count";
So the simple trick that i used was to refactor the useState(1) to useState(x) ..
so to that x can be updated from the current count value..
Below is the Refactored Code...
How to get the changed state after an async action, using React functional hooks? I have found a redux solution for this issue, or a react class component solution, but I am wondering if there is a simple react functional solution.
Here is the scenario:
create a functional react component with. few states
create several button elements that each alter a different state.
using one of the button elements, trigger an async action.
If there was any other change in the state, prior to receiving results from the async function, abort all other continuing actions.
Attached is a code sandbox example
https://codesandbox.io/s/magical-bird-41ty7?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [counter, setCounter] = useState(0);
const [asyncCounter, setAsyncCounter] = useState(0);
return (
<div className="App">
<div>
<button
onClick={async () => {
//sets the asyncCounter state to the counter states after a 4 seconds timeout
let tempCounter = counter;
await new Promise(resolve => {
setTimeout(() => {
resolve();
}, 4000);
});
if (tempCounter !== counter) {
alert("counter was changed");
} else {
setAsyncCounter(counter);
}
}}
>
Async
</button>
<label>{asyncCounter}</label>
</div>
<div>
<button
onClick={() => {
//increases the counter state
setCounter(counter + 1);
}}
>
Add 1
</button>
<label>{counter}</label>
</div>
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You can use a ref to keep track of the counter value independently
const [counter, setCounter] = useState(0);
const counterRef = useRef(counter)
Whenever you update counter you update counterRef as well:
const newCounter = counter + 1
setCounter(newCounter);
counterRef.current = newCounter
And then check it:
if (counterRef.current !== counter) {
alert("counter was changed");
} else {
setAsyncCounter(counter);
}
Codesandox
I've found another answer by asking the question on facebook-rect github.
Apparently, since setting a state is a function, it's first argument is the current state.
so it is possible to have access to the previous value by using this snippet when setting the counter value:
setCounter(prevValue => {
alert(prevValue + " " + counter);
return counter + 1;
});
https://github.com/facebook/react/issues/19270
https://reactjs.org/docs/hooks-reference.html#functional-updates
As #thedude mentioned, you will need to use the useRef hook – it was made exactly for your use case, as the docs say: "It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes."
I think you might just want to add a simple boolean:
const counterChanged = useRef(false);
and then when you update the counter, you update this too.
counterChanged.current = true;
setCounter(counter + 1);
and inside your async function, you set it to false and then check if it's been changed.
counterChanged.current = false;
await new Promise(resolve => {
setTimeout(() => {
resolve();
}, 4000);
});
if (counterChanged.current) {
// alert