I've got a clock function that gets time and renders out the hours, minutes and seconds, and I'm trying to update the data on screen in real time, but for some reason my setInterval function isn't doing what I expect.
I thought react's render method is supposed to render data in real time. Do I need ajax for this? Can anyone offer some advice?
var CityRow = React.createClass({
render: function() {
var currentdate = new Date();
function getTime() {
// get local time based on the UTC offset
this.hours = currentdate.getUTCHours() + parseInt(this.props.UTCOffset);
// correct for number over 24, and negatives
if( this.hours >= 24 ){ this.hours = this.hours - 24; }
if( this.hours < 0 ){ this.hours = this.hours + 12; }
// add leading zero, first convert hours to string
this.hours = this.hours + "";
if( this.hours.length == 1 ){ this.hours = "0" + this.hours; }
// minutes are the same on every time zone
this.minutes = currentdate.getUTCMinutes();
// add leading zero, first convert hours to string
this.minutes = this.minutes + "";
if( this.minutes.length == 1 ){ this.minutes = "0" + this.minutes; }
this.seconds = currentdate.getUTCSeconds();
}
window.setInterval(function () {
getTime();
}, 1000);
return(
<div className="city-row" ref="cityRow">
<span className="city-time">{this.hours}:{this.minutes}:{this.seconds}</span>
</div>
</div>
)
}
});
The official React Docs describe exactly what you need (and even explains why you should do it as described):
--> React Docs: State and Lifecycle
Example on CodePen:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
There seems to be a couple problems with your code. First is the closing div in the render function which causes your element to not even render.
Next you might want to take a look at state/props and React general lifecycle methods to get a better idea of program flow. The setInterval should be put in componentDidMount so its not called every time your component renders and creates a lot of timers. It also might help to put hours, minutes, and seconds as state so that way when these change your component re-renders automatically.
I modified your code below and put an example on jsfiddle. It does not print the seconds properly (just like in your getTime method) but hopefully it will give you a better idea of how logic should flow.
https://jsfiddle.net/rpg6t4uc/
var CityRow = React.createClass({
setTime: function(){
var currentdate = new Date();
var hours = currentdate.getUTCHours() + parseInt(this.props.UTCOffset);
// correct for number over 24, and negatives
if( hours >= 24 ){ hours -= 24; }
if( hours < 0 ){ hours += 12; }
// add leading zero, first convert hours to string
hours = hours + "";
if( hours.length == 1 ){ hours = "0" + hours; }
// minutes are the same on every time zone
var minutes = currentdate.getUTCMinutes();
// add leading zero, first convert hours to string
minutes = minutes + "";
if( minutes.length == 1 ){ minutes = "0" + minutes; }
seconds = currentdate.getUTCSeconds();
console.log(hours, minutes, seconds)
this.setState({
hours: hours,
minutes: minutes,
seconds: seconds
});
},
componentWillMount: function(){
this.setTime();
},
componentDidMount: function(){
window.setInterval(function () {
this.setTime();
}.bind(this), 1000);
},
render: function() {
return(
<div className="city-row" ref="cityRow">
<span className="city-time">{this.state.hours}:{this.state.minutes}:{this.state.seconds}</span>
</div>
)
}
});
functional component
function Time() {
const [time, setTime] = React.useState(new Date());
React.useEffect(() => {
setInterval(() => {
setTime(new Date());
}, 1000);
}, []);
return <span>{time.toLocaleString("en-US", {
dateStyle: "full",
timeStyle: "medium",
hour12: false,
})} </span>;
}
Update of "TheSchecke" answer to show UTC date on format "YYYY-MM-DD HH:mm:ss":
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
const ISOStringDate = this.state.date.toISOString();
return (
<div>
<h1>UTC Time:</h1>
<h2>{ISOStringDate.substring(0, 19).replace('T', ' ')}</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
class Clock extends React.Component {
state ={time:null}
componentDidMount() {
setInterval(() => {
this.setState({time:new Date().toLocaleTimeString()})}, 1000)
}
render() {
return (
<div className="time">
The time is: {this.state.time}
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root') );
import React, { Component } from "react";
class Clock extends Component {
constructor (props) {
super(props);
this.state = {
dateClass: new Date()
}
this.time = this.state.dateClass.toLocaleTimeString();
this.hourMin = this.time.length === 10? this.time.slice(0) : this.time.slice(0,5);
}
setTime = () => {
this.setState({
dateClass: new Date()
})
this.time = this.state.dateClass.toLocaleTimeString();
this.hourMin = this.time.length === 10? this.time.slice(0) : this.time.slice(0,5);
}
componentDidMount () {
setInterval(this.setTime, 1000)
}
render () {
return (
<div>
{this.hourMin}
</div>
)
}
}
export default Clock;
Hey, I have more simple approach.
Related
Hello mighty people from internet,
I am trying to build a countdown timer app with pause and reset buttons using React hooks.
The timer countdown should stop once pause or reset button gets clicked performed by function pauseCountDown() and reset(), respectively.
However, the timer does not stop after pauseCountDown() or reset() executed.
I don’t quite understand why setInterval() does not stop after clearInterval() gets executed.
clearInterval() should be executed when isPaused === true.
However, the value of “isPaused” instantly switches back to false along with the next iteration of nonstopped setInterval().
Any ideas or thoughts what I missed or mistakes I have?
Thank you.
Here is the code of the child component performing timer countdown:
function TimerPanel(props) {
var launch;
var secLeft = parseInt(props.timerNow.min) * 60 + parseInt(props.timerNow.sec)
const startCountDown = () => {
props.setIsPaused(false)
launch = setInterval(decrement, 1000)
}
function decrement() {
if (secLeft > 0 && !props.isPaused) {
secLeft--;
var minCountDown = Math.floor(secLeft / 60)
var secCountDown = Math.floor((secLeft % 60) * 100) / 100
props.setTimerNow({...props.timerNow, min: minCountDown, sec: secCountDown})
}
}
const pauseCountDown = () => {
props.setIsPaused(true)
clearInterval(launch)
}
const reset = () => {
props.setIsPaused(false)
clearInterval(launch)
props.setSessionLength(props.initialTimer.min);
props.setTimerNow({...props.timerNow, min: props.initialTimer.min, sec: props.initialTimer.sec})
}
Since launch is just an ordinary variable, its value will not survive across rerenderings of TimerPanel. You'll need to store it inside a ref with useRef
// add useRef to your imports from react
import { useRef } from "react";
function TimerPanel(props) {
const launch = useRef();
var secLeft =
parseInt(props.timerNow.min) * 60 + parseInt(props.timerNow.sec);
const startCountDown = () => {
props.setIsPaused(false);
clearInterval(launch.current) // clean up any old timers
launch.current = setInterval(decrement, 1000);
};
function decrement() {
if (secLeft > 0 && !props.isPaused) {
secLeft--;
var minCountDown = Math.floor(secLeft / 60);
var secCountDown = Math.floor((secLeft % 60) * 100) / 100;
props.setTimerNow({
...props.timerNow,
min: minCountDown,
sec: secCountDown,
});
}
}
const pauseCountDown = () => {
props.setIsPaused(true);
clearInterval(launch.current);
};
const reset = () => {
props.setIsPaused(false);
clearInterval(launch.current);
props.setSessionLength(props.initialTimer.min);
props.setTimerNow({
...props.timerNow,
min: props.initialTimer.min,
sec: props.initialTimer.sec,
});
};
// ...
}
My function is supposed to switch when she is working. Basically , it's 11pm , she is supposed to switch if it's 11:01pm but i have to reload the app to make it work. Do you know how to switch the function without reload all the app ?
startMatchOrStop = () => {
return this.state.sport.clubs.map((element) => {
var dateMatch = new Date(element.dateMatch).getTime();
var dateMatchStop = new Date(element.dateMatchStop).getTime();
var today = Date.now();
console.log(dateMatch);
console.log(dateMatchStop);
console.log(today);
if (today >= dateMatch && today < dateMatchStop) {
return (
this.setState({soundCapture: true}),
RNSoundLevel.start(),
(RNSoundLevel.onNewFrame = (data) => {
this.setState({soundArray: data.value});
this.PostDecibels();
console.log(today >= dateMatchStop);
})
);
} else {
return (
console.log(today <= dateMatchStop),
RNSoundLevel.stop(),
this.setState({soundCapture: false}),
Alert.alert('Titre', 'Match Fini enregistrement terminé')
);
}
});
};
If you want your function to re-run again
you may put it inside interval
and check it every X seconds for Y time(s).
for example:
var retryCount = 3;
var startFuncInt = setInterval(() => {
if (--retry){
startMatchOrStop();
}
else{
window.clearInterval(startFuncInt);
}
}, 1000);
I am working on a HTML customelement, but when I run it in the browser, I get a very interesting error. This is the code:
class clock extends HTMLElement {
constructor() {
super()
this.time = new Date(this.getAttribute('time'));
this.span = document.createElement('span')
this.appendChild(this.span)
}
Timer() {
let Now = this.time;
let Seconds = Now.getSeconds();
let Minutes = Now.getMinutes();
let Hours = Now.getHours();
Minutes = Minutes < 10 ? "0" + Minutes : Minutes;
document.getElementById("digitalClock").textContent = Hours + ":" + Minutes;
}
connectedCallback(){
this.span.innerHTML = `...<span id="digitalClock"></span>`;
this.Timer();
this.myClockTimer = setInterval(this.Timer, 5000);
}
disconnectedCallback(){
clearInterval(this.myClockTimer);
}
}
customElements.define('worktime-clock', clock)
When I run this, the Timer function runs well when calling with this.Timer() in the connectedCallback function, but one line after, when it gets into a cycle, it says that Now is undefined in Timer function. It seems like there is a problem about calling it in setinterval, however, the function is definitely running again and again as expected. Does someone know what is the problem?
You lose the correct this context by passing the Timer function as a callback. As a result this (within the callback) now points to window instead. You can use bind to set the this-context:
this.myClockTimer = setInterval(this.Timer.bind(this), 5000);
This accomplishes the same thing:
var that = this;
this.myClockTimer = setInterval(function() { that.Timer(); }, 5000);
Another alternative:
this.myClockTimer = setInterval(function() { this.Timer(); }.bind(this), 5000);
Or an arrow function
<script>
customElements.define('worktime-clock', class extends HTMLElement {
updateTime() {
let Now = new Date();
const pad = x => String(x).padStart(2,'0');
const H = pad(Now.getHours());
const M = pad(Now.getMinutes());
const S = pad(Now.getSeconds());
this.innerHTML = `${H}:${M}:${S}`;
}
connectedCallback() {
this.myClockTimer = setInterval(() => {
this.updateTime();
}, 1e3);
}
})
</script>
<worktime-clock>21:22:23</worktime-clock>
Or for a code-golf competition
<script>
customElements.define('worktime-clock', class extends HTMLElement {
connectedCallback() {
setInterval(() => {
this.innerHTML = new Date().toString().split` `[4];
}, 1e3);
}
})
</script>
<worktime-clock>21:22:23</worktime-clock>
I am practically new to React. In this App I am using Hooks!
I've made a Countdown Timer that will show in a few seconds after logging in. I am unable to make it stop on a button click. I need some advise on this as I've been struggling for the past 2 days with this.
This is my code so far: (Please Help)
function Admin() {
const [isTimerOpen, setTimmer] = useState(false);
let history = useHistory();
// SET BY THE ADMIN
var minutesToCountDown = 0.9;
// TRANSFORM INTO SECONDS
var transformMinutesToSeconds = minutesToCountDown * 60
// KEEP A STATE
const [counterValue, setCounterValue] = useState(0);
const [isTimmerStoped, setStopTimer] = useState(false);
// FUNCTION TO HAPPEN EVERY 1 SECOND
function timeIt() {
if (isTimmerStoped === false) {
transformMinutesToSeconds--
setCounterValue(transformMinutesToSeconds)
console.log("Timer is on: ", transformMinutesToSeconds)
if (transformMinutesToSeconds === 0) {
clearInterval(interval)
setStopTimer(true)
}
} else {
setStopTimer(true)
clearInterval(interval)
}
}
// STARTS THE COUNTDOWN
var interval;
const startCountdown = () => {
interval = setInterval(timeIt, 1000)
}
const stopCountdown = () => {
console.log("Stop Timer")
setStopTimer(true);
setCounterValue(0);
setTimmer(false);
}
// ADD 0 IN FRONT ON THE TIME REMAINING
const addLeadingZeros = value => {
value = String(value);
while (value.length < 2) {
value = `0${value}`;
}
return value;
};
// CONVERT SECONDS INTO TIME REMAINING
function convertSeconds(seconds) {
var min = Math.floor(seconds / 60);
var sec = seconds % 60;
return addLeadingZeros(min) + ':' + addLeadingZeros(sec)
}
const logOutUser = () => {
logout();
return history.push(mainRoute)
}
function setTimer() {
const timer = setTimeout(() => {
setTimmer(true)
console.log('This will run after 3 seconds!')
startCountdown()
}, sessionTimeout);
return () => clearTimeout(timer);
}
useEffect(() => {
if (isTimmerStoped === false) {
console.log('Effect Starting', isTimmerStoped)
setTimer()
} else {
console.log('Effect Stopping', isTimmerStoped)
stopCountdown()
}
}, [isTimmerStoped, setStopTimer, minutesToCountDown]);
return <React.Fragment>
<CssBaseline />
<Container disableGutters maxWidth={false}>
<NavigationBar handleSignOut={logOutUser}/>
<TimerContent
timeRemaining={convertSeconds(counterValue)}
isTimerAlertOpen={isTimerOpen}
extendSessionBtn={stopCountdown}
logoutBtn={logOutUser}
clickOutsideButton={stopCountdown}/>
</Container>
</React.Fragment>
}
export default Admin;
You should do 2 things.
Make the interval variable a ref. This way it's value will be unique every where it imported. Note: Just creating a variable above the component is a bad idea because that variable will be shared between each component that imports the Admin component, which will lead to bugs.
Wrong
let interval;
function Admin() {
//... code here
// STARTS THE COUNTDOWN
// var interval; Remove from here
const startCountdown = () => {
interval = setInterval(timeIt, 1000)
}
//... code here
}
export default Admin;
Right
function Admin() {
const interval = React.useRef();
//... code here
// STARTS THE COUNTDOWN
// var interval; Remove from here
const startCountdown = () => {
interval.current = setInterval(timeIt, 1000)
}
//... code here
}
export default Admin;
Add clearInterval to your stopCountdown function. You can remove the clearInterval in the timeIt function and move it into stopCountdown. Please take a look at this codepen I made to demostrate
const stopCountdown = () => {
console.log("Stop Timer")
setStopTimer(true);
setCounterValue(0);
setTimmer(false);
clearInterval(interval.current) // Clear the interval here
}
You shouldn't maintain the intervalId returned by setInterval in a functional variable since it will be reset on re-render and you will loose the actual timer Id. You must instead use useRef hook to store the timerId
const interval = useRef(null);
...
function timeIt() {
if (isTimmerStoped === false) {
transformMinutesToSeconds--
setCounterValue(transformMinutesToSeconds)
console.log("Timer is on: ", transformMinutesToSeconds)
if (transformMinutesToSeconds === 0) {
clearInterval(interval.current)
setStopTimer(true)
}
} else {
setStopTimer(true)
clearInterval(interval.current)
}
}
...
// STARTS THE COUNTDOWN
const startCountdown = () => {
interval.current = setInterval(timeIt, 1000)
}
I made a custom audio player in React that updates the so called "Scrubber" of the audio file (progressbar) with the value based on duration of the player. Everything works fine except that if you press the play/pause button when the interval updates (mousedown -> interval updates state -> mouseup) then the button click doesn't fire.
import React, { Component } from 'react';
import ReactSVG from 'react-svg';
import VolumeButton from 'components/VolumeButton/VolumeButton.jsx';
import play from 'img/icons/play.svg';
import pause from 'img/icons/pause.svg';
import './AudioPlayer.css';
class AudioPlayer extends Component {
constructor(props) {
super(props);
this.state = {
currentTime: '00:00',
endTime: '00:00',
intervalsInitiated: false,
player: null,
playing: false,
progressBar: null
};
this.handleResize = this.handleResize.bind(this);
}
initIntervals = () => {
let intervals = setInterval(() => {
this.updateScrubber(this.state.player.currentTime);
this.updateTime(this.state.player.currentTime);
}, 100);
this.setState({
intervals: intervals,
intervalsInitiated: true
});
};
setUpAudioTime = player => {
console.log('setup time');
let playerCurrentTime = player.currentTime;
let currentTime = this.calculateTime(playerCurrentTime);
this.setState({
currentTime: currentTime,
endTime: this.calculateTime(player.duration)
});
this.updateScrubber(playerCurrentTime);
};
initPlayer = () => {
console.log('initPlayer');
// Add resize listener for showing volume on desktop
this.addResize();
this.handleResize();
// Setup correct player
let player = new Audio(this.props.audioPath);
let progressBar = document.querySelector('.ProgressBar');
// Add event for audio stopped for play button
player.addEventListener('ended', () => this.toggleAudioPlay());
// Set state to correct player
// TODO: Do this in update aswell going from one to another
this.setState({
player: player,
progressBar: progressBar
});
player.addEventListener('loadedmetadata', () =>
this.setUpAudioTime(player)
);
};
toggleAudioPlay = () => {
console.log('toggling audio');
if (!this.state.player) {
return;
}
!this.state.intervalsInitiated
? this.initIntervals()
: this.resetComponent();
this.state.playing
? this.state.player.pause()
: this.state.player.play();
this.setState({
playing: !this.state.playing
});
};
updateScrubber = playerCurrentTime => {
if (!this.state.player || !this.state.progressBar) {
return;
}
let prog = this.state.progressBar;
prog.value = playerCurrentTime / this.state.player.duration;
this.setState({
progressBar: prog
});
};
updateTime = playerCurrentTime => {
this.setState({
currentTime: this.calculateTime(playerCurrentTime)
});
};
calculateTime = lengthInSeconds => {
let minutes = Math.floor(lengthInSeconds / 60);
let seconds = Math.floor(lengthInSeconds) - minutes * 60;
let time =
(minutes < 10 ? '0' + minutes : minutes) +
':' +
(seconds < 10 ? '0' + seconds : seconds);
return time;
};
seek = event => {
if (!this.state.player || !this.state.progressBar) {
return;
}
let percent =
event.nativeEvent.offsetX / this.state.progressBar.offsetWidth;
let prog = this.state.progressBar;
prog.value = percent;
this.setState({ progressBar: prog });
let newTime = percent * this.state.player.duration;
let player = this.state.player;
player.currentTime = newTime;
this.setState({
player: player
});
this.updateTime(newTime);
};
resetComponent() {
console.log('reset component');
clearInterval(this.state.intervals);
this.setState({
intervalsInitiated: false
});
}
componentDidMount() {
this.initPlayer();
}
render() {
return (
<div
className={`AudioPlayer ${
this.state.width < 800 ? 'AudioPlayer--extended' : ''
}`}
>
<audio className="MarkerAudio" preload="metadata">
<source src={this.props.audioPath} type="audio/mp3" />
Your device doesnt support audio format.
</audio>
<div className="PlayerControls">
<div className="PlayButton" onClick={this.toggleAudioPlay}>
{this.state.playing ? (
<ReactSVG
className="AudioIcon"
svgStyle={{ height: '100%', width: '100%' }}
path={pause}
/>
) : (
<ReactSVG
className="AudioIcon"
svgStyle={{
height: '100%',
left: '2px',
position: 'relative',
width: '100%'
}}
path={play}
/>
)}
</div>
<span className="CurrentTime">
{this.state.currentTime}
</span>
<progress
onClick={this.seek}
className="ProgressBar"
value="0"
min="0"
max="1"
/>
<span>{this.state.endTime}</span>
</div>
</div>
);
}
}
export default AudioPlayer;
However, the click still works if I make sure I do a "fast-click" or if the interval isn't initiated. Any ideas on how to handle this? Should I use a onMouseDown or something instead? Or is there anyway to make sure that the interval and state change doesn't interfere with the onClick?
So for anyone wondering it had something do to with click on the icon not bubbling properly when state updated. All I had to do to fix this was to add pointer-events: none; on the icon so the click gets triggered directly on the div and not on icon.