Task to do StopWatch on RxJs using Observables - javascript

I have a task to do StopWatch. I did it using Subject, but I should do it via Observables.
I read about at rxjs.dev. But I can`t understand it. Help me plz
The stopwatch must have the following buttons:
"Start / Stop" - start / stop timing, stops and resets the stopwatch value.
"Wait" - works on a double click (the time between clicks is not more than 300 ms!) The stopwatch should stop timing; if you press start after it, then the countdown is resumed.
"Reset" - reset the stopwatch to 0. Resets the stopwatch and starts counting again.
import "./App.css";
import { useEffect, useState } from "react";
import { interval, Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
function App() {
const [time, setTime] = useState(0);
const [status, setStatus] = useState(false);
useEffect(() => {
const unsubscribe$ = new Subject();
interval(10)
.pipe(takeUntil(unsubscribe$))
.subscribe(() => {
if (status === true) {
setTime(val => val + 10);
}
});
return () => {
unsubscribe$.next();
unsubscribe$.complete();
};
}, [status]);
const start = React.useCallback(() => {
setStatus(true);
}, []);
const stop = React.useCallback(() => {
setStatus(false);
}, []);
const reset = React.useCallback(() => {
setTime(0);
setStatus(false);
}, []);
console.log(time);
return (
<div className="App">
<h1>Секундомер</h1>
<span>{Math.floor(time / (1000 * 60 * 60)) % 24}</span>
<span>{Math.floor((time / (1000 * 60)) % 60)}</span>
<span>{Math.floor((time / 1000) % 60)}</span>
<div>
<button onClick={()=> start()} class="start">Старт</button>
<button onClick={()=> stop()}class="stop">Стоп</button>
<button onClick={()=> reset()} class="reset">Перезапуск</button>
</div>
</div>
);
}
export default App;```

I created a pretty generic stopwatch implementation a while back.
type StopwatchAction = "START" | "STOP" | "RESET" | "END";
function createStopwatch(
control$: Observable<StopwatchAction>,
interval = 1000
): Observable<number>{
return defer(() => {
let toggle: boolean = false;
let count: number = 0;
const ticker = timer(0, interval).pipe(
map(x => count++)
);
const end$ = of("END");
return concat(
control$,
end$
).pipe(
catchError(_ => end$),
switchMap(control => {
if(control === "START" && !toggle){
toggle = true;
return ticker;
}else if(control === "STOP" && toggle){
toggle = false;
return EMPTY;
}else if(control === "RESET"){
count = 0;
if(toggle){
return ticker;
}
}
return EMPTY;
})
);
});
}
Using this is just a matter of hooking the correct events into a stream of StopwatchActions. This example only sends "START" and "RESET" based on button clicks, but you can merge in "STOP" and "END" as well.
For example:
createStopwatch(merge(
fromEvent(startBtn, 'click').pipe(mapTo("START")),
fromEvent(resetBtn, 'click').pipe(mapTo("RESET"))
)).subscribe(seconds => {
secondsField.innerHTML = seconds % 60;
minuitesField.innerHTML = Math.floor(seconds / 60) % 60;
hoursField.innerHTML = Math.floor(seconds / 3600);
});

Related

Why does Uncaught SyntaxError: Identifier has already been declared error occurs?

I am new to programming and currently trying to build a timer. I am using JS when I encountered a problem on the very first line, it said that my function has already been declared when it hasn't. I read lots of other stack questions related to this error but none seems to have the same problem. Here is my code:
const timer = () => { // This is where the error occurs.
const song = document.querySelectorAll(".song");
const play = document.querySelector(".play");
const reset = document.querySelectorAll(".reset");
// Time display
const minuteDisplay = document.querySelectorAll(".minute");
const secondDisplay = document.querySelectorAll(".second");
//Duration
const formDuration = 20;
let duration = formDuration * 60;
let displayMinutes = ("0" + Math.floor(duration / 60)).slice(-2);
let displaySeconds = ("0" + Math.floor(duration % 60)).slice(-2);
for (const mdisplay in minuteDisplay) {
mdisplay.textContent = `${displayMinutes}`;
}
for (const sdisplay in secondDisplay) {
sdisplay.textContent = `${displaySeconds}`;
}
play.addEventListener("click", () => {
checkPlaying(song);
});
const checkPlaying = (song) => {
if (song.paused) {
song.play();
play.textContent = `Pause`;
play.classList.toggle("btn-active");
} else {
song.pause();
play.innerHTML = `Play`;
play.classList.remove("btn-active");
}
};
song.ontimeupdate = () => {
let currentTime = song.currentTime;
let elapsed = duration - currentTime;
let seconds = Math.floor(elapsed % 60);
let minutes = Math.floor(elapsed / 60);
// Edit time display
formatMinutes = ("0" + minutes).slice(-2);
formatSeconds = ("0" + seconds).slice(-2);
minuteDisplay.textContent = `${formatMinutes}`;
secondDisplay.textContent = `${formatSeconds}`;
reset.addEventListener("click", () => {
song.pause();
song.currentTime = 0;
play.innerHTML = `Play`;
play.classList.remove("btn-active");
if (reset.classList.contains("btn-active")) return;
reset.classList.add("btn-active");
// remove class after 2 seconds
setTimeout(() => {
reset.classList.remove("btn-active");
}, 150);
});
if (currentTime >= duration) {
song.pause();
song.currentTime = 0;
play.innerHTML = "Play";
play.classList.remove("btn-active");
}
};
};
timer();
I am confused as how to solve this error, so any feedback will be greatly appreciated. Thankyou!
Hi I checked your when you are using querySeelctorAll it returns array so before adding addEventListener you have to do for loop like this
let's do it for reset button:
const reset = document.querySelectorAll(".reset");
and now first for loop
reset.forEach(resetButton => {
resetButton.addEventListener("click", () => {
song.pause();
song.currentTime = 0;
play.innerHTML = `Play`;
play.classList.remove("btn-active");
if (reset.classList.contains("btn-active")) return;
reset.classList.add("btn-active");
// remove class after 2 seconds
setTimeout(() => {
reset.classList.remove("btn-active");
}, 150);
});
})
Or you can use loop like this:
for(const resetButton of reset) {
resetButton.addEventListener("click", () => {
song.pause();
song.currentTime = 0;
play.innerHTML = `Play`;
play.classList.remove("btn-active");
if (reset.classList.contains("btn-active")) return;
reset.classList.add("btn-active");
// remove class after 2 seconds
setTimeout(() => {
reset.classList.remove("btn-active");
}, 150);
});
}
Hope this would help.
And for debugging why "Uncaught SyntaxError: Identifier has already been declared error occurs" happens I need to have your HTML part too.
let me know when you updated the code

unable to clear interval, React native

I created a timer function it's working but when I click clearInterval it's not working, timer still going on.
Here is my function to start timer. Maximum limit for timer is 60sec
const StartRecord = ()=>{
const timeout = setInterval(() => {
if (time != 60) {
setTime(prevState => prevState + 1);
}
}, 1000);
console.log(timeout);
if (time == 60) {
clearInterval(timeout);
}
}
here is my function to stop timer
const onStopRecord = () => {
clearInterval(time);
}
can anyone tell me why it's not working?
You are trying clearInterval using time. It should be timeout
const onStopRecord = () => {
clearInterval(timeout);
}
you can use useEffect to do this task
useEffect(() => {
let interval
if(time != 60){
interval = setInterval(() => setTime((prev) => prev + 1), 1000);
}
return () => clearInterval(interval);
}, [time])

setInterval() and clearInterval() in React

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,
});
};
// ...
}

React: How to force a function to run after another one is totally completed?

In this React Pomodoro Clock, there is a function countDown. In it there is a function called three. Currently, when this.setState({ init: 'break' }); is set in two, three occurs immediately. However, three should wait until two completes. Any help would be greatly appreciated.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './style.css';
/*
* A simple React component
*/
const initState = {
breakLength: 5,
breakSeconds: undefined,
sessionLength: 25,
sessionSeconds: undefined,
init: 'session',
timeLeft: undefined,
timeLeftSeconds: undefined,
started: false,
intervalFunc: undefined
}
const secondsToMins = (time) => {
//let converted = Math.floor(time / 60) + ':' + ('0' + Math.floor(time % 60)).slice(-2);
let converted = ('0' + Math.floor(time / 60)).slice(-2) + ':' + ('0' + Math.floor(time % 60)).slice(-2);
//console.log('converted')
//console.log(converted)
//console.log('#######')
return converted;
}
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = initState;
this.breakDecrement = this.breakDecrement.bind(this);
this.breakIncrement = this.breakIncrement.bind(this);
this.sessionDecrement = this.sessionDecrement.bind(this);
this.sessionIncrement = this.sessionIncrement.bind(this);
this.startStop = this.startStop.bind(this);
this.reset = this.reset.bind(this);
}
componentDidMount() {
// seconds are used for the countDown()
// seconds are converted to MM:SS at every t-minus
let breakSeconds = this.state.breakLength * 60;
let sessionSeconds = this.state.sessionLength * 60;
// Initialize everything
this.setState({ breakSeconds: breakSeconds });
this.setState({ sessionSeconds: sessionSeconds });
this.setState({ timeLeftSeconds: sessionSeconds });
this.setState({ timeLeft: secondsToMins(sessionSeconds) });
}
breakDecrement() {
// decrements the breakLength and the breakSeconds
// breakLength is only a number ie. 5 (does not show seconds)
// breakSeconds is that nunber converted into seconds
let breakLength = this.state.breakLength - 1;
if (breakLength > 0 && breakLength < 61){
this.setState({ breakLength: breakLength });
let breakSeconds = breakLength * 60;
this.setState({ breakSeconds: breakSeconds });
}
}
breakIncrement() {
// same as decrement except does increment
let breakLength = this.state.breakLength + 1;
if (breakLength > 0 && breakLength < 61){
this.setState({ breakLength: breakLength });
let breakSeconds = breakLength * 60;
this.setState({ breakSeconds: breakSeconds });
}
}
sessionDecrement() {
// decrements the sessionLength and the sessionSeconds
// sessionLength is only a number ie. 25 (does not show seconds)
// sessionSeconds is that nunber converted into seconds
let sessionLength = this.state.sessionLength - 1;
if (sessionLength > 0 && sessionLength < 61){
this.setState(prevState => ({
sessionLength: prevState.sessionLength-1,
sessionSeconds: (prevState.sessionLength-1)*60,
timeLeftSeconds: (prevState.sessionLength-1)*60,
timeLeft: secondsToMins((prevState.sessionLength-1)*60)})
);
}
}
sessionIncrement() {
// same as decrement except does increment
let sessionLength = this.state.sessionLength + 1;
if (sessionLength > 0 && sessionLength < 61){
this.setState(prevState => ({
sessionLength: prevState.sessionLength+1,
sessionSeconds: (prevState.sessionLength+1)*60,
timeLeftSeconds: (prevState.sessionLength+1)*60,
timeLeft: secondsToMins((prevState.sessionLength+1)*60)})
);
}
}
startStop(id) {
// starts the countDown, which runs continuously until the start/stop button
// is pressed again, which pauses the countdown.
// the id parameter is used by countDown to play the audio beep
if(!this.state.started){
this.countDown(id);
this.setState({ started: true});
}
// pauses the countDown
if(this.state.started){
let intervalFunc = this.state.intervalFunc;
clearInterval(intervalFunc);
this.setState({ started: false});
}
}
reset() {
let intervalFunc = this.state.intervalFunc;
clearInterval(intervalFunc);
// reset state to default values
this.setState({ breakLength: 5 });
this.setState({ sessionLength: 25 });
this.setState({ init: 'session' });
this.setState({ timeLeftSeconds: 1500})
this.setState({ timeLeft: '25:00' });
}
countDown(id){
// set the function to a variable and set state to it, so the function
// can be paused with clearInterval()
var intervalFunc = setInterval(() => down(this.state.timeLeftSeconds--), 1000);
this.setState({intervalFunc: intervalFunc});
const down = (time) =>
{
const one = async () =>{
if(time > 0){
// converts seconds to MM:SS at every t-minus
this.setState({ timeLeft: secondsToMins(time)});
console.log(time);
console.log(this.state.timeLeft);
}
return;
}
//when time reaches 0 set state.init to break
// and set seconds left to break seconds
const two = async () => {
if(time == 0 && this.state.init == 'session'){
this.setState({ timeLeft: secondsToMins(time)});
let sound = document.getElementById(id).childNodes[0];
this.setState({ init: 'break' });
console.log(this.state.init)
sound.play();
this.setState({ timeLeftSeconds: this.state.breakSeconds});
}
return;
}
//when time reaches 0 set state.init to session
// and set seconds left to session seconds
const three = async () => {
if(time == 0 && this.state.init == 'break'){
this.setState({ timeLeft: secondsToMins(time)});
let sound = document.getElementById(id).childNodes[0];
this.setState({ init: 'session' });
console.log(this.state.init)
sound.play();
this.setState({ timeLeftSeconds: this.state.sessionSeconds});
}
return;
}
one().then(two).then(three);
}
}
render() {
return (
<div id="clock">
<h1 id="title">25-5 Clock</h1>
<div>
<p id="break-label">Break Length</p>
<p id="break-length">{this.state.breakLength}</p>
<button id="break-decrement" onClick={e => this.breakDecrement()}> Decrease </button>
<button id="break-increment" onClick={e => this.breakIncrement()}> Increase </button>
</div>
<div>
<p id="session-label">Session Length</p>
<p id="session-length">{this.state.sessionLength}</p>
<button id="session-decrement" onClick={e => this.sessionDecrement()}> Decrease </button>
<button id="session-increment" onClick={e => this.sessionIncrement()}> Increase </button>
</div>
<hr/>
<div>
<p id="timer-label">{this.state.init}</p>
<p id="time-left">{this.state.timeLeft}</p>
<button id="start_stop" onClick={e => this.startStop(e.target.id)}><audio id="beep" src='./beep.mp3'></audio> start/stop </button>
<button id="reset" onClick={e => this.reset()}> reset </button>
</div>
</div>
);
}
};
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Clock />, document.getElementById("app"));
index.html:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>25-5 Clock</title>
<style>
</style>
</head>
<body>
<main>
<div id="app"></app>
</main>
</body>
</html>
You need to make them asynchronous.
like...
const one = async () => {
//some code
return
}
const two = async () => {
//some code
return
}
const three = async () => {
//some code
return
}
Then you can...
one().then(two).then(three)
You can do that by making the functions return a Promise and using the async / await keywords to wait until they're finished before starting the next.
const setDelay = delay => new Promise(resolve => {
console.log(`Process running for ${delay}`);
setTimeout(() => {
console.log('Process done');
resolve();
}, delay);
});
(async () => {
await setDelay(2000);
await setDelay(3000);
await setDelay(1000);
})();
Or you could do without async / await and chain the promises.
const setDelay = delay => new Promise(resolve => {
console.log(`Process running for ${delay}`);
setTimeout(() => {
console.log('Process done');
resolve();
}, delay);
});
setDelay(3000)
.then(() => setDelay(1000))
.then(() => setDelay(4000));
Or just use good 'ol fashioned callbacks. But I'd go with one of the above.
The solution:
Use an array to hold states and duration:
const states = [ { name: 'session', duration: 1500 }, { name: 'break', duration: 300 } ]
Alternate the index of the array to alternate between session and break.
countDown(id){
// set the function to a variable and set state to it, so the function
// can be paused with clearInterval()
var intervalFunc = setInterval(() => down(this.state.timeLeftSeconds--), 1000);
this.setState({intervalFunc: intervalFunc});
const down = (time) =>
{
if(time > 0){
// converts seconds to MM:SS at every t-minus
this.setState({ timeLeft: secondsToMins(time)});
console.log(time);
console.log(this.state.timeLeft);
}
if (time <= 0) {
this.setState({ timeLeft: secondsToMins(time)});
let sound = document.getElementById(id).childNodes[0];
sound.play();
let stateIndex = (this.state.stateIndex + 1) % states.length;
this.setState({ stateIndex: stateIndex});
this.setState({ timeLeftSeconds: states[stateIndex].duration});
this.setState({ init: states[stateIndex].name});
console.log(this.state.init);
}
}
}

React Countdown Timer unable to make a stop function

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

Categories