I rewrote my class component to a functional component but I this I made a mistake somewhere. I can find out what it is. The timer keeps going when the time is up. I think its the useEffect that i use wrong? The class componed worked fine but I need the functional to use redux.
import React, {useEffect, useState} from 'react';
import '../style/App.scss';
function CountdownTimer(){
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
useEffect(() => {
if(running){
handleStart()
}
});
let timer = null;
const [time, setTime] = useState(0.1*60);
const [running, setRunning] = useState(true);
const handleStart= () => {
if (running){
timer = setInterval(() => {
if (time === 0){
console.log("no more time");
handleStop()
}else{
const newTime = time - 1;
setTime(
newTime >= 0 ? newTime : handleStop
);
}
}, 1000);
}
}
const handleStop = () => {
if(timer) {
clearInterval(timer);
setRunning(false);
}
}
const format = (time) => {
const date = new Date(time * 1000);
let hh = date.getUTCHours();
let mm = date.getUTCMinutes();
let ss = date.getSeconds();
if (hh < 10) {hh = "0"+hh;}
if (mm < 10) {mm = "0"+mm;}
if (ss < 10) {ss = "0"+ss;}
return '00' !== hh ? hh+":"+mm+":"+ss : mm+":"+ss;
}
return(
<div className="icon-value">
<h1>{format(time)}</h1>
</div>
);
}
export default CountdownTimer;
Related
I'm trying to display a different colour depending on the amount of days between 2 dates. I've successfully got my differences between the two dates to work, but i'm struggling with updating the colour as it doesn't update straight away as my 'diff' doesn't update.
Should I be doing this in a different way? I've tried using the useEffect hook to try and sequence them but its given me errors.
import { useEffect, useState } from "react"
const ColourDisplay = ({ date }) => {
const [colour, setColour] = useState("green")
const [plantDate, setPlantDate] = useState(new Date(date))
const [diff, setDiff] = useState(0)
let colourPicker = () => {
if (diff <= 0) {
setColour('red')
} else if (diff > 0 && diff <= 2) {
setColour('orange')
} else {
setColour('green')
}
}
let diffCalculator = () => {
const today = new Date()
const _MS_PER_DAY = 1000 * 60 * 60 * 24;
const utc1 = Date.UTC(plantDate.getFullYear(), plantDate.getMonth(), plantDate.getDate());
const utc2 = Date.UTC(today.getFullYear(), today.getMonth(), today.getDate());
setDiff(Math.floor((utc1 - utc2) / _MS_PER_DAY))
}
useEffect(() => {
setPlantDate(new Date(date))
diffCalculator(colourPicker)
}, [date])
if (date) {
return <div style={{ backgroundColor: `${colour}` }}>color</div>
}
}
export default ColourDisplay
If anyone can help me out with what i'm doing wrong here then that would be amazing.
Thanks all.
I have updated the code
You have used several useless states, one state is enough to show the result.
import { useEffect, useState } from "react";
import * as React from "react";
const ColourDisplay = ({ date }) => {
const [colour, setColour] = useState("green");
let colourPicker = (diff) => {
if (diff <= 0) {
setColour("red");
} else if (diff > 0 && diff <= 2) {
setColour("orange");
} else {
setColour("green");
}
};
let diffCalculator = (plantDate) => {
const today = new Date();
const _MS_PER_DAY = 1000 * 60 * 60 * 24;
const utc1 = Date.UTC(
plantDate.getFullYear(),
plantDate.getMonth(),
plantDate.getDate()
);
const utc2 = Date.UTC(
today.getFullYear(),
today.getMonth(),
today.getDate()
);
colourPicker(Math.floor((utc1 - utc2) / _MS_PER_DAY));
};
useEffect(() => {
diffCalculator(new Date(date));
}, [date]);
if (date) {
return <div style={{ backgroundColor: `${colour}` }}>color</div>;
}
};
export default ColourDisplay;
This is normal timer in React Native
There is something wrong with countDown function, I've been trying to figure it out for hours already.
I check line by line useEffect function is right, convertingTotalTime also works fine.
There must be something wrong with setState functions, as they are not syncronus, but CountDown function is called every second, you can try code snippet ...
can you please help me understand what's wrong with code?
import React, { useEffect, useState } from "https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js";
import { Button, StyleSheet, Text, View } from "https://cdnjs.cloudflare.com/ajax/libs/react-native-web/0.17.1/index.js";
export default function App() {
const [totalTime, setTotalTime] = useState(50000);
const [playMode, setPlayMode] = useState(false);
const [progress, setProgress] = useState(100);
const [timeString, setTimeString] = useState(`00 : 00`);
const [second, setSec] = useState(0);
const [minute, setMin] = useState(0);
useEffect(() => {
if (playMode && totalTime > 0) {
let myInterval = setInterval(() => {
countDown();
console.log("countdown");
return () => {
clearInterval(myInterval);
};
}, 1000);
}
}, [playMode]);
const countDown = () => {
// console.table(hour, minute, second);
let [sec, min] = [second, minute];
let _totalTime = totalTime;
_totalTime = _totalTime - 1000;
sec = sec - 1;
if (sec == 0 && min >= 1) {
min = min - 1;
}
if (min == 0 && sec == 0) {
// coundown finished
console.warn("counter must stop");
}
setSec((prevState) => sec);
setMin((prevState) => min);
setTotalTime((prevState) => _totalTime);
setTimeString(
`${min < 10 ? "0" + min : min} : ${sec < 10 ? "0" + sec : sec} `
);
// this function is for later use => circular progress bar
setProgress((prevProgress) => {
if (prevProgress == 0) {
return 100;
}
let x = 100 / _totalTime;
return progress - x;
});
};
const convertTotalTime = () => {
let timeToConvert = totalTime;
let min = +Math.floor(timeToConvert / 60 / 1000);
timeToConvert -= Math.floor(timeToConvert / 1000);
setMin((prevState) => prevState + min);
let sec = +Math.floor(timeToConvert / 1000);
timeToConvert -= Math.floor(timeToConvert);
setSec((prevState) => prevState + sec);
};
const startTimerHandler = () => {
convertTotalTime();
setPlayMode(true);
};
return (
<View style={styles.container}>
<Text>{timeString}</Text>
<Button onPress={startTimerHandler} title="Start" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-native-web/0.17.1/index.js"></script>
Your import seems to be wrong...
import React, { useEffect, useState } from "react";
import { Button, StyleSheet, Text, View } from "react-native";
Cheers, I hope you solved your problem!
So the answer to this is following
In class based Components we usually destructure the state, update value and setIt again.
See Code Below
```
export default function App() {
const [second, setSec] = useState(0);
const [minute, setMin] = useState(0);
...
const countDown = () => {
// state destructure
let [sec, min] = [second, minute];
// update
sec = sec - 1;
// set it again
setSec(sec)
// the problem is console.log(second == sec) FALSE
// here state wont update for no reason....
// the following is kind of fix
setSec(prevState => prevState -1)
// state updates now BUT console.log(second == sec) FALSE !
// code below won't ever work
if (minute == 0 && second == 0) {
// coundown finished
console.warn("counter must stop");
}
}
```
So here is the only solution I found ...
```
export default function App() {
const [second, setSec] = useState(0);
const [minute, setMin] = useState(0);
...
const countDown = () => {
let [sec, min] = [second, minute];
// keep track of both state and local variable ...
sec = sec - 1;
setSec((prevState) => prevState - 1);
if (sec == 0 && min >= 1) {
min = min - 1;
setMin((prevState) => prevState - 1);
}
if (min == 0 && sec == 0) {
// coundown finished
console.warn("counter must stop");
}
...
};
```
I have the following usage of my hook, but it doesn't use the new timerDuration when I update inside my input:
const [secondsBetweenRepsSetting, setSecondsBetweenRepsSetting] = useState(DEFAULT_SECONDS_BETWEEN_REPS)
const {secondsLeft, isRunning, start, stop} = useTimer({
duration: secondsBetweenRepsSetting,
onExpire: () => sayRandomExerciseName(),
onTick: () => handleTick(),
});
const onTimeBetweenRepsChange = (event: any) => {
const secondsBetweenRepsSettingString = event.target.value;
const secondsBetweenRepsSettingInt = parseInt(secondsBetweenRepsSettingString)
setSecondsBetweenRepsSetting(secondsBetweenRepsSettingInt)
}
return <React.Fragment>
<input type="number" name="secondsBetweenRepsSetting" value={secondsBetweenRepsSetting} onChange={onTimeBetweenRepsChange}/>
</React.Fragment>
And here is the implementation of the useTimer hook, which I'm not sure why it's not getting my duration update?
import { useState } from 'react';
import Validate from "../utils/Validate";
import useInterval from "./useInterval";
export default function useTimer({ duration: timerDuration, onExpire, onTick}) {
const [duration] = useState(timerDuration)
const [secondsLeft, setSecondsLeft] = useState(timerDuration)
const [isRunning, setIsRunning] = useState(false)
function start() {
setIsRunning(true)
}
function stop() {
setIsRunning(false)
}
function handleExpire() {
Validate.onExpire(onExpire) && onExpire();
}
useInterval(() => {
const secondsMinusOne = secondsLeft - 1;
setSecondsLeft(secondsMinusOne)
if(secondsMinusOne <= 0) {
setSecondsLeft(duration) // Reset timer automatically
handleExpire()
} else {
Validate.onTick(onTick) && onTick();
}
}, isRunning ? 1000 : null)
return {secondsLeft, isRunning, start, stop, }
}
Remove the line:
const [duration] = useState(timerDuration);
You are already getting duration from timerDuration, just use that.
I'm trying to implement countdown timer on my own just to know hooks more. I know there are libraries out there but don't want to use it. the problem with my code is, I cannot get updated state inside "timer" function which is updated in start timer function I'm trying to implement timer that will have triggers to start, stop, & resume & can be manually trigger. by other component that is using the countdown component
import React, { useState } from 'react';
const Countdown = ({ countDownTimerOpt }) => {
const [getObj, setObj] = useState({
formatTimer: null,
countDownTimer: 0,
intervalObj: null,
});
const { formatTimer, countDownTimer, intervalObj } = getObj;
if (countDownTimerOpt > 0 && intervalObj === null) {
startTimer();
}
function startTimer() {
const x = setInterval(() => {
timer();
}, 1000);
setObj((prev) => ({
...prev,
countDownTimer: countDownTimerOpt,
intervalObj: x,
}));
}
function timer() {
var days = Math.floor(countDownTimer / 24 / 60 / 60);
var hoursLeft = Math.floor(countDownTimer - days * 86400);
var hours = Math.floor(hoursLeft / 3600);
var minutesLeft = Math.floor(hoursLeft - hours * 3600);
var minutes = Math.floor(minutesLeft / 60);
var remainingSeconds = countDownTimer % 60;
const formatTimer1 =
pad(days) +
':' +
pad(hours) +
':' +
pad(minutes) +
':' +
pad(remainingSeconds);
if (countDownTimer === 0) {
clearInterval(intervalObj);
} else {
setObj((prev) => ({
...prev,
formatTimer: formatTimer1,
countDownTimer: prev['countDownTimer'] - 1,
}));
}
}
function pad(n) {
return n < 10 ? '0' + n : n;
}
return <div>{formatTimer ? formatTimer : Math.random()}</div>;
};
export default Countdown;
import React, { useState, useEffect } from 'react';
import Timer from '../../components/countdown-timer/countdown.component';
const Training = () => {
const [getValue, setValue] = useState(0);
useEffect(() => {
const x = setTimeout(() => {
console.log('setTimeout');
setValue(10000);
}, 5000);
return () => clearInterval(x);
}, []);
return <Timer countDownTimerOpt={getValue} />;
don't want to use any set interval inside training page as the countdown component will also be used in exam page
Usually with hooks I would combine your functionality into a custom hook and use it in different places.
const useTimer = (startTime) => {
const [time, setTime] = useState(startTime)
const [intervalID, setIntervalID] = useState(null)
const hasTimerEnded = time <= 0
const isTimerRunning = intervalID != null
const update = () => {
setTime(time => time - 1)
}
const startTimer = () => {
if (!hasTimerEnded && !isTimerRunning) {
setIntervalID(setInterval(update, 1000))
}
}
const stopTimer = () => {
clearInterval(intervalID)
setIntervalID(null)
}
// clear interval when the timer ends
useEffect(() => {
if (hasTimerEnded) {
clearInterval(intervalID)
setIntervalID(null)
}
}, [hasTimerEnded])
// clear interval when component unmounts
useEffect(() => () => {
clearInterval(intervalID)
}, [])
return {
time,
startTimer,
stopTimer,
}
}
You can of course add a reset function or do other changes but use could look like this:
const Training = () => {
const { time, startTimer, stopTimer } = useTimer(20)
return <>
<div>{time}</div>
<button onClick={startTimer}>start</button>
<button onClick={stopTimer}>stop</button>
</>
}
You can create a useCountDown Hook as follow (In Typescript) :
Gist
import { useEffect, useRef, useState } from 'react';
export const useCountDown: (
total: number,
ms?: number,
) => [number, () => void, () => void, () => void] = (
total: number,
ms: number = 1000,
) => {
const [counter, setCountDown] = useState(total);
const [startCountDown, setStartCountDown] = useState(false);
// Store the created interval
const intervalId = useRef<number>();
const start: () => void = () => setStartCountDown(true);
const pause: () => void = () => setStartCountDown(false);
const reset: () => void = () => {
clearInterval(intervalId.current);
setStartCountDown(false);
setCountDown(total);
};
useEffect(() => {
intervalId.current = setInterval(() => {
startCountDown && counter > 0 && setCountDown(counter => counter - 1);
}, ms);
// Clear interval when count to zero
if (counter === 0) clearInterval(intervalId.current);
// Clear interval when unmount
return () => clearInterval(intervalId.current);
}, [startCountDown, counter, ms]);
return [counter, start, pause, reset];
};
Usage Demo: https://codesandbox.io/s/usecountdown-hook-56lqv
I am trying to make a countdown timer using React hooks. The seconds part of the timer is working as expected, but I am encountering an issue when updating the minute part. In the below example, I want the timer to start from 05:00 and then on click of a button update to 04:59, 04:58 and so on. But when I click the button, instead of giving 04:59, it gives me 03:59. Attaching the code for the same below. Please let me know where I am getting it wrong.
import React, { useState } from "react";
const padWithZero = num => {
const numStr = num.toString();
return numStr.length === 1 ? "0" + numStr : numStr;
};
const Clock = () => {
let timer;
const [mins, setMins] = useState(5);
const [secs, setSecs] = useState(0);
const startHandler = () => {
timer = setInterval(() => {
setSecs(prevSecs => {
if (prevSecs === 0) {
setMins(prevMins => prevMins - 1);
return 59;
} else return prevSecs - 1;
});
}, 1000);
};
return (
<div>
<h1>{`${padWithZero(mins)}:${padWithZero(secs)}`}</h1>
<button onClick={startHandler}>Start</button>
</div>
);
};
export default Clock;
I don't know the exact reason why it decreases your minutes two times from 5 to 4 and from 4 to 3.
But I modified your code and added resetting feature - each click to button will resets timer back to initial 5:00. And now it works correctly:
import React, { useState } from "react";
const padWithZero = num => {
const numStr = num.toString();
return numStr.length === 1 ? "0" + numStr : numStr;
};
const INIT_SECS = 0;
const INIT_MINS = 5;
const Clock = () => {
let timer;
const [mins, setMins] = useState(INIT_MINS);
const [secs, setSecs] = useState(INIT_SECS);
const [storedTimer, setStoredTimer] = useState(null);
const startHandler = () => {
if (storedTimer) {
clearInterval(storedTimer);
setMins(INIT_MINS);
setSecs(INIT_SECS);
}
const newTimer = setInterval(() => {
setSecs(prevSecs => {
if (prevSecs === 0) {
setMins(prevMins => prevMins - 1);
return 59;
} else return prevSecs - 1;
});
}, 1000);
setStoredTimer(newTimer);
};
return (
<div>
<h1>{`${padWithZero(mins)}:${padWithZero(secs)}`}</h1>
<button onClick={startHandler}>Start</button>
</div>
);
};
export default Clock;