How to unsubscribe from setinterval only when input updated - javascript

I need some help with set interval.
I have 2 fields, 1st is an input for insert milleseconds for interval , and the second one is the count itself.
I need to run counter according to the milleseconds that inserted to the input:
import "./styles.css";
import { useState, useEffect, useRef } from "react";
export default function App() {
const [count, setCount] = useState(0);
const [ms, setMS] = useState(1000);
useEffect(() => {
const interval = setInterval(() => {
console.log("Start inteval");
setCount(count + 1);
}, ms);
return () => {
console.log("Clear interval");
clearInterval(interval);
};
});
return (
<div className="App">
<input value={ms} onChange={(e) => setMS(e.target.value)}></input>
<h1>{count}</h1>
</div>
);
}
For now it's working, but the problem is in my return (unsubscribe method).
I need to clear the interval only when the input changed and I need to update the ms for interval.
Some one for best solution pls?

You need to specify ms as dependency in your useEffect and also you need to increase counter by 1 with previous state
useEffect(() => {
const interval = setInterval(() => {
console.log('Start inteval');
setCount(prevCount => prevCount + 1); // replace with this to update counter correctly
}, ms);
return () => {
console.log('Clear interval');
clearInterval(interval);
};
}, [ms]); // add dependency here

Related

What is a simplest way to create timer and stopwatch in react native?

I want to create a timer or stopwatch for my video player project to count views, for that I want a simple stopwatch to enter my logic for the view counter.
I tried creating the stopwatch and timer using setInterval but that didn't work as I was expecting.
you can make a customHook for timer
import React, { useEffect, useState } from "react";
const useTimer = (delay) => {
const [state, setState] = useState(0)
const [stop, isSetStop] = useState(false)
useEffect(() => {
if (stop) return
console.log("TIMER RUNNING=>>>>>>>>>>>");
setTimeout(() => {
setState((prev => prev + 1))
}, delay)
}, [state])
return [state, () => isSetStop(true)]
}
export default useTimer
use it like this
const [time, stopTime] = useTimer(1000)
you can reverse it and it will be stopwatch and you can call stopTime method to stop it or you can put condition in useEffect of hook
useEffect(() => {
if (stop || state >= 60 ) return
console.log("TIMER RUNNING=>>>>>>>>>>>");
setTimeout(() => {
setState((prev => prev + 1))
}, delay)
}, [state])

why does the component render 3 times?

I have a component like this:
import { useEffect, useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
console.log("comp run");
const tick = () => {
setCount(count + 1);
console.log(count);
};
useEffect(() => {
const timer = setInterval(tick, 10000);
console.log("effect run");
return () => {
clearInterval(timer);
console.log("clear func run");
};
}, []);
return <div>{count}</div>;
}
export default Counter;
When the code runs, the console outputs as follows:
Output immediately when the program runs:
comp run
effect run
after 10 seconds :
comp run
0
after 10 seconds :
comp run
0
after 10 seconds :
0 (then it keeps increasing by 0 every ten seconds)
What I don't understand here is exactly this: "comp run" is printed on the screen 3 times. Why 3 ?
This is because useEffect memoize all values inside it. You can use two ways:
Add count to useEffect's dependencies array. And when count changes, useEffect will refreshed.
useEffect(() => {
//Your old code here
}, [count]); //Here
Create a function inside of useCallback hook and memoize function for better performance. Works like in first way, but dependent by tick fucntion, wich dependent by count state.
const tick = useCallback(() => {
setCount(count + 1);
console.log(count);
}, [count]);
useEffect(() => {
const timer = setInterval(tick, 1000);
console.log("effect run");
return () => {
clearInterval(timer);
console.log("clear func run");
};
}, [tick]);

How can I update my function to show the clock that ticks every second

Please refer my code below
var DisplayTime = () => {
return <h1 style={{ color: "white" }}> {new Date().toLocaleTimeString()}</h1>;
};
setInterval(DisplayTime,1000);
export default DisplayTime;
It shows the time when the function is called. I want it to update every second.
Create a state variable to store the time in, inside a useEffect you can create an interval to update the time every second. Don't forget to clearInterval inside the useEffect cleanup.
import { useEffect, useState } from "react";
export default function App() {
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <h1>{time}</h1>;
}
Try it out in this Sandbox.

Unable to access state inside interval

I am unable to access the state within an interval. Here I want to access counter inside interval when the counter gets equal to 10 I want to stop it.
Note: Here I don't want to put an interval inside useEffect because I need to start the interval in a specific time, by handling an event.
export default props => {
const [counter, setCounter] = useState(0);
const startInterval = () => {
const timeout = setInterval(() => {
setCounter(counter + 1);
console.log("counter: ", counter); // alway return 0
if(counter === 10) clearInterval(timeout);
}, 1000);
};
}
As I am seeing here even your setCounter(counter+1) wont update, because of lexical scope. So you have to change it like this:
setCounter(counter => counter + 1);
Also because of lexical scope you wont access counter to check condition, so you have to make a variable and update that inside functional component by asigning counter to it, then check it with if condition.
Complete Code code:
let myCounter = 0;
let timeout = null;
export default CounterApp = props => {
const [counter, setCounter] = useState(0);
// Also don't forget this
useEffect(()=> {
return ()=> clearInterval(timeout);
}, []);
myCounter = counter;
const startInterval = () => {
timeout = setInterval(() => {
setCounter(counter => counter + 1);
console.log("counter: ", myCounter); // counter always return 0 but myCounter the updated value
if(myCounter === 10) clearInterval(timeout);
}, 1000);
};
}
I came across this exact problem not too long ago. Hooks don't work exactly as you'd expect in relation to setInterval. I found the solution on Dan Abramov's blog: You can useRef to combine multiple useEffects. Through his useInterval implementation you can also start and stop the interval by setting the timer to null
import React, { useState, useEffect, useRef } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [time, setTime] = useState(null); // timer doesn't run initially
useInterval(() => {
// Your custom logic here
setCount(count + 1);
}, time);
return <>
<h1>{count}</h1>
<div onClick={() => setTime(1000)}>Start</div>
<div onClick={() => setTime(null)}>Stop</div>
</>;
}
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}

Using ref react hooks multiple times

I'm trying to use ref API many times with setInterval method but only one is working
I have created a ref as an array then I'm trying to insert the function to the array by using the index key but that works only for the first insert I don't know what I'm doing wrong
here's what I've achieved
import React, { useState, useEffect, useRef, createRef } from "react";
import ReactDOM from "react-dom";
function Counter() {
const [countSec, setCountSec] = useState(0);
const [countMin, setCountMin] = useState(0);
useInterval(() => {
setCountSec(countSec + 1); // this working
}, 1000, 0);
useInterval(() => {
setCountMin(countMin + 1); // it's not working
}, 1100, 1);
return <div>
<h1>{countSec} Secounds</h1>
<h1>{countMin} Half-Minutes</h1>
</div>;
}
function useInterval(callback, delay,index){
const savedCallback = useRef([...Array(2)].map(()=> createRef()));
// Remember the latest function.
useEffect(() => {
savedCallback.current[index].current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
let id = setInterval(()=>savedCallback.current[index].current(), delay);
return () => clearInterval(id);
});
};
https://codesandbox.io/s/sharp-tree-k39ts
There is no need to try to remember multiple callbacks in your useRef. Everytime you use useInterval it creates a new instance of your hook. So they will work independently from eachother. This means the first time you use useInterval it will create a new useRef for your setCountSec(countSec + 1) callback. And the second time you use useInterval it will create another instance of useRef for your setCountMin(countMin + 1). All you have to do it change the interval to have the second one update once every 30 seconds.
import React, { useState, useEffect, useRef, createRef } from "react";
import ReactDOM from "react-dom";
function Counter() {
const [countSec, setCountSec] = useState(0);
const [countMin, setCountMin] = useState(0);
useInterval(() => {
setCountSec(countSec + 1);
}, 1000); // update every 1000ms (1sec)
useInterval(() => {
setCountMin(countMin + 1);
}, 30000); // update every 30000ms (30sec)
return <div>
<h1>{countSec} Secounds</h1>
<h1>{countMin} Half-Minutes</h1>
</div>;
}
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest function.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
Based on your comment
you need to update countMin after countSec
Here is an working code
function Counter() {
const [countSec, setCountSec] = useState(0);
const [countMin, setCountMin] = useState(0);
useEffect(()=>{
setInterval(() => {
setCountSec(countSec + 1); // this working
}, 1000);
setInterval(() => {
setCountMin(countMin + 1); // it's not working
}, 1100);
}, [])
return <div>
<h1>{countSec} Secounds</h1>
<h1>{countMin} Half-Minutes</h1>
</div>;
}
few pointers on your code would be for every rerender the whole function would get called and a new setInterval would be created...
useEffect(()=>{},[]) is equivalent to componentDidMount
if you want to follow your patten without this useEffect(()=>{},[]) you could use setTimeout

Categories

Resources