Convert 5 minutes to 100% width on React Javascript - reactjs

So I'm trying to build progress bar and convert minutes to % so I can apply them to the progress below. When the time is 00 then the progress should be 100% filled (or the opposite not filled, doesn't matter)
Here is what I've build until now:
import * as styles from './Screen.module.scss'
import { useState, useEffect } from 'react'
export default function Screen() {
const [countDown, setCountDown] = useState(0);
const [runTimer, setRunTimer] = useState(true);
useEffect(() => {
let timerId;
if (runTimer) {
setCountDown(60 * 5);
timerId = setInterval(() => {
setCountDown((countDown) => countDown - 1);
}, 1000);
} else {
clearInterval(timerId);
}
return () => clearInterval(timerId);
}, [runTimer]);
useEffect(() => {
if (countDown < 0 && runTimer) {
console.log("expired");
setRunTimer(false);
setCountDown(0);
}
}, [countDown, runTimer]);
const seconds = String(countDown % 60).padStart(2, 0);
const minutes = String(Math.floor(countDown / 60)).padStart(2, 0);
const [prg, setPrg] = useState(0);
const fill = {
width: `${prg}%`,
height: '100%',
backgroundColor: 'green',
transform: 'width 0.45s ease-in-out'
}
return (
<main className={styles.container}>
<div className={styles.bar}>
<div className={styles.bar_value}>{minutes}:{seconds}</div>
<div style={fill}></div>
</div>
</main>
)
}

prg shouldn't be state, since it's derived solely from the duration of your countdown and the number of seconds left. (You might want to make 60 * 5 a constant, a prop, or state though.)
Simply:
const prg = (countDown / (60 * 5)) * 100;
will have your progress bar count down from 100% to 0%.
To have it count up,
const prg = 100 - (countDown / (60 * 5)) * 100;

Related

Changing the colour depending on date difference - React

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;

unable to update name and value every 10 seconds

I am trying to implement react counter. every 10 seconds I need to update label and progress bar. But in label I could able to display 1 to 6 in 60 seconds successfully. but in timer due to some issue even though it reaches 60 seconds progress bar percange showing 80% only.
timer logic
const [number, setNumber] = useState(0);
const [progBarVal, setProgBarValr] = useState(0);
useEffect(() => {
if (number >= 6) {
return;
}
const intervalID = setTimeout(() => {
setNumber((t) => t + 1);
setProgBarValr((t) => t + 10);
}, 1000);
return () => clearInterval(intervalID);
}, [number, progBarVal]);
with in the return statement
return{
<div > {number}/6 </div>
<Progress done={progBarVal} />
}
progress bar logic
import React, { useState } from 'react';
import './progress.scss';
const Progress = ({ done }) => {
const [style, setStyle] = useState({});
setTimeout(() => {
const newStyle = {
opacity: 1,
width: `${done}%`,
};
setStyle(newStyle);
}, 200);
return (
<div className='met-prog__progress'>
<div className='met-prog__progress-done' style={style}>
{done}%
</div>
</div>
);
};
export default Progress;
I am trying to do if number is 1 the progBarVal 10 like that.
someone help me to understand where it went wrong.

React Timer value get struck after it reaches 12 sec

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

React Timer, React Native Timer,

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

React js converting a class to functions

I currently have a timer that works with a class but I need to use only functions so it works with the App.js file I am running to show what I need. Here is an example of how I would call my file from the App:
return (
<div>
<h1>Example Timer</h1>
<h2>Other files have been removed for example purposes</h2>
<Timer onClick ={this.startTimer} />
</div>
)
And here is my Timer class which needs to be converted to functions so I can export it like:
export default function Timer(onClick)
Here is the entire class component for the timer
import React from 'react';
import "./timer.css";
class Timer extends React.Component{
constructor(){
super();
this.state = { time: {}, seconds: 5, color: 'darkgrey'};
this.timer = 0;
this.startTimer = this.startTimer.bind(this);
this.startCountDown = this.startCountDown.bind(this);
}
convertToSeconds(sec){
let divisor_for_minutes = sec % (60 * 60);
var minutes = Math.floor(divisor_for_minutes / 60);
let divisor_for_seconds = divisor_for_minutes % 60;
var seconds = Math.ceil(divisor_for_seconds);
var obj = {"m": minutes, "s": seconds};
return obj;
}
componentDidMount() {
let timeLeftVar = this.convertToSeconds(this.state.seconds);
this.setState({ time: timeLeftVar });
}
startTimer(){
// If timer is not set, set the interval
if(this.timer === 0 && this.state.seconds > 0){
// 1000 ms = 1 second
this.timer = setInterval(this.startCountDown, 1000);
}
}
startCountDown() {
let seconds = this.state.seconds - 1;
if(seconds >= 0){
if(seconds<=2){
this.setState({color: 'red'})
}
this.setState({
time: this.convertToSeconds(seconds),
seconds: seconds
});
}
else{
this.setState({time: this.convertToSeconds(5), seconds: 5, color: 'darkgrey'});
clearInterval(this.timer);
this.timer = 0;
}
}
render(){
return(
<div className="component-timer">
<div class="startbtn">
<button onClick={this.startTimer}>Start</button>
</div>
<div class="timer"><div style = {{color: this.state.color}}>
m: {this.state.time.m} s: {this.state.time.s}
</div>
</div>
</div>
);
}
}
export default Timer;
With a new version of react we can use Hook useState and it will look like this
import React, { useState } from 'react';
import "./timer.css";
const convertToSeconds = (sec) => {
let divisor_for_minutes = sec % (60 * 60);
var minutes = Math.floor(divisor_for_minutes / 60);
let divisor_for_seconds = divisor_for_minutes % 60;
var seconds = Math.ceil(divisor_for_seconds);
var obj = {"m": minutes, "s": seconds};
return obj;
}
const Timer= () => {
const [seconds, setSeconds] = useState(5)
const [time, setTime] = useState(convertToSeconds(seconds))
const [color, setColor] = useState('darkgrey')
const [timer, setTimer] = useState(0)
const startTimer = () => {
// If timer is not set, set the interval
if(timer === 0 && seconds > 0){
// 1000 ms = 1 second
setTimer(setInterval(startCountDown, 1000))
}
}
const startCountDown = () => {
let newSeconds = seconds - 1;
if(newSeconds >= 0){
if(newSeconds<=2){
setColor('red')
}
setTime(convertToSeconds(newSeconds))
setSeconds(newSeconds)
}
else{
setTime(convertToSeconds(5))
setSeconds(5)
setColor('darkgrey')
setTimer(0)
}
}
return(
<div className="component-timer">
<div class="startbtn">
<button onClick={startTimer}>Start</button>
</div>
<div class="timer"><div style = {{color}}>
m: {time.m} s: {time.s}
</div>
</div>
</div>
)
}
export default Timer
This is the line by line conversion of the class component to function component using react hooks... (You need react version higher than 16.8.x https://reactjs.org/docs/hooks-intro.html)
import React, { useState, useEffect } from 'react';
import './timer.css';
export default function Timer() {
const [seconds, setSeconds] = useState(5);
const [time, setTime] = useState({});
const [color, setColor] = useState('darkgrey');
const [timer, setTimer] = useState(0);
//only executes when component mounts by passing "[]".
useEffect(() => {
setTime(convertToSeconds(seconds));
}, []);
function convertToSeconds(sec) {
let divisor_for_minutes = sec % (60 * 60);
var minutes = Math.floor(divisor_for_minutes / 60);
let divisor_for_seconds = divisor_for_minutes % 60;
var seconds = Math.ceil(divisor_for_seconds);
var obj = { m: minutes, s: seconds };
return obj;
}
function startTimer() {
// If timer is not set, set the interval
if (timer === 0 && seconds > 0) {
// 1000 ms = 1 second
setTimer(setInterval(startCountDown, 1000));
}
}
function startCountDown() {
let _seconds = seconds - 1;
if (_seconds >= 0) {
if (_seconds <= 2) {
setColor('red');
}
setTime(convertToSeconds(_seconds));
setSeconds(_seconds);
} else {
setTime(convertToSeconds(5));
setSeconds(5);
setColor('darkgrey');
clearInterval(timer);
setTimer(0);
}
}
return (
<div className="component-timer">
<div className="startbtn">
<button onClick={startTimer}>Start</button>
</div>
<div className="timer">
<div style={{ color: color }}>
m: {time.m} s: {time.s}
</div>
</div>
</div>
);
}
You do not need function to work, you can use your class component and use it to render in directly in index.js or anywhere you want to render
Code is somewhat like this
import React from 'react';
import ReactDOM from 'react-dom';
import Timer from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(
<div>
<h1>Example Timer</h1>
<h2>Other files have been removed for example purposes</h2>
<Timer />
</div>
, document.getElementById('root'));
registerServiceWorker();

Resources