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");
}
...
};
```
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;
I tried to create a react timer that counts 20 seconds and then stops at 0 seconds. But the problem is it gets struck randomly in between 14,13 or 12 seconds and they keep repeating the same value again and again. Here is my code.
import React, { useEffect,useState } from 'react';
const Airdrop = () => {
const [timer,setTimer] = useState(0);
const [time,setTime] = useState({});
const [seconds,setSeconds] = useState(20);
const startTimer = () =>{
if(timer === 0 && seconds > 0){
setInterval(countDown,1000);
}
}
const countDown = ()=>{
let secondsValue = seconds - 1;
let timeValue = secondsToTime(secondsValue);
setTime(timeValue);
setSeconds(secondsValue);
if(secondsValue === 0){
clearInterval(timer);
}
}
const secondsToTime = (secs)=>{
let hours,minutes,seconds;
hours = Math.floor(secs/(60*60));
let devisor_for_minutes = secs % (60*60);
minutes = Math.floor(devisor_for_minutes/60);
let devisor_for_seconds = devisor_for_minutes % 60;
seconds = Math.ceil(devisor_for_seconds);
let obj = {
"h": hours,
"m": minutes,
"s": seconds
}
return obj;
}
useEffect(() => {
let timeLeftVar = secondsToTime(seconds);
setTime(timeLeftVar);
}, []);
useEffect(() => {
startTimer();
});
return (
<div style={{color:"black"}}>
{time.m}:{time.s}
</div>
)
}
export default Airdrop
When you are using multiple state and they each depend on each other it is more appropriate to use a reducer because state updates are asynchronous. You might update one state based on a stale value.
However in your case, we don't need a reducer because we can derive all the data we need from a single state.
When you set the state, it is not immediately updated and it might cause issues when the next state depend on the last one. Especially when you use it like this:
const newState = state-1;
setState(newState);
However with functional updates we can directly use the last state to set the next one.
setState((prevState)=> prevState-1);
I took the liberty of creating helper functions to make your component leaner. You can copy them in a file in /helpers and import them directly.
I also replaced the interval with a timeout because it is easy get a situation where we don't clear the interval. For example the component is unmounted and without finishing the timer. We then get an interval running indefinitely.
import React, { useEffect, useState } from 'react';
const getHours = (duration) => {
const hours = Math.floor(duration / 3600);
if (hours < 10) return '0' + hours.toString();
return hours.toString();
};
const getMinutes = (duration) => {
const minutes = Math.floor((duration - +getHours(duration) * 3600) / 60);
if (minutes < 10) return '0' + minutes.toString();
return minutes.toString();
};
const getSeconds = (duration) => {
const seconds =
duration - +getHours(duration) * 3600 - +getMinutes(duration) * 60;
if (seconds < 10) return '0' + seconds.toString();
return seconds.toString();
};
const Airdrop = (props) => {
const { duration = 20 } = props;
const [seconds, setSeconds] = useState(duration);
useEffect(() => {
if (seconds === 0) return;
const timeOut = setTimeout(() => {
setSeconds((prevSeconds) => {
if (prevSeconds === 0) return 0;
return prevSeconds - 1;
});
}, 1000);
return () => {
clearTimeout(timeOut);
};
}, [seconds]);
return (
<div style={{ color: 'black' }}>
{getHours(seconds)}:{getMinutes(seconds)}:{getSeconds(seconds)}
</div>
);
};
export default Airdrop;
You are creating a new interval each time the component is rendered because one of the useEffect hooks is missing a dependency array.
const startTimer = () => {
if (timer === 0 && seconds > 0) {
setInterval(countDown,1000);
}
}
useEffect(() => {
startTimer();
});
I suggest the following:
Capture the timer id in a React ref, use a mounting useEffect hook to return a cleanup function to clear any running intervals if/when component unmounts
Remove the time state, it is derived state from the seconds state, just compute it each render cycle
Add a dependency to the useEffect hook starting the timer
Move secondsToTime utility function outside component
Code
const secondsToTime = (secs) => {
let hours, minutes, seconds;
hours = Math.floor(secs / (60 * 60));
let devisor_for_minutes = secs % (60 * 60);
minutes = Math.floor(devisor_for_minutes / 60);
let devisor_for_seconds = devisor_for_minutes % 60;
seconds = Math.ceil(devisor_for_seconds);
return {
h: hours,
m: minutes,
s: seconds
};
};
...
const Airdrop = () => {
const timerRef = useRef(null);
const [seconds, setSeconds] = useState(20);
useEffect(() => {
return () => clearInterval(timerRef.current); // <-- clean up any running interval on unmount
}, []);
const startTimer = () => {
if (timerRef.current === null && seconds > 0) {
timerRef.current = setInterval(countDown, 1000); // <-- capture timer id to stop interval
}
};
const countDown = () => {
setSeconds((seconds) => seconds - 1); // <-- interval callback only decrement time
};
useEffect(() => {
if (seconds === 0) {
clearInterval(timerRef.current);
timerRef.current = null;
setSeconds(20);
}
}, [seconds]); // <-- check seconds remaining to kill interval/reset
useEffect(() => {
startTimer();
}, []); // <-- only start timer once
const time = secondsToTime(seconds); // <-- compute time
return (
<div style={{ color: "black" }}>
{time.m}:{time.s}
</div>
);
};
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;
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;