Unable to access state inside interval - javascript

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

Related

How to unsubscribe from setinterval only when input updated

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

React interval using old state inside of useEffect

I ran into a situation where I set an interval timer from inside useEffect. I can access component variables and state inside the useEffect, and the interval timer runs as expected. However, the timer callback doesn't have access to the component variables / state. Normally, I would expect this to be an issue with "this". However, I do not believe "this" is the the case here. No puns were intended. I have included a simple example below:
import React, { useEffect, useState } from 'react';
const App = () => {
const [count, setCount] = useState(0);
const [intervalSet, setIntervalSet] = useState(false);
useEffect(() => {
if (!intervalSet) {
setInterval(() => {
console.log(`count=${count}`);
setCount(count + 1);
}, 1000);
setIntervalSet(true);
}
}, [count, intervalSet]);
return <div></div>;
};
export default App;
The console outputs only count=0 each second. I know that there's a way to pass a function to the setCount which updates current state and that works in this trivial example. However, that was not the point I was trying to make. The real code is much more complex than what I showed here. My real code looks at current state objects that are being managed by async thunk actions. Also, I am aware that I didn't include the cleanup function for when the component dismounts. I didn't need that for this simple example.
The first time you run the useEffect the intervalSet variable is set to true and your interval function is created using the current value (0).
On subsequent runs of the useEffect it does not recreate the interval due to the intervalSet check and continues to run the existing interval where count is the original value (0).
You are making this more complicated than it needs to be.
The useState set function can take a function which is passed the current value of the state and returns the new value, i.e. setCount(currentValue => newValue);
An interval should always be cleared when the component is unmounted otherwise you will get issues when it attempts to set the state and the state no longer exists.
import React, { useEffect, useState } from 'react';
const App = () => {
// State to hold count.
const [count, setCount] = useState(0);
// Use effect to create and clean up the interval
// (should only run once with current dependencies)
useEffect(() => {
// Create interval get the interval ID so it can be cleared later.
const intervalId = setInterval(() => {
// use the function based set state to avoid needing count as a dependency in the useEffect.
// this stops the need to code logic around stoping and recreating the interval.
setCount(currentCount => {
console.log(`count=${currentCount}`);
return currentCount + 1;
});
}, 1000);
// Create function to clean up the interval when the component unmounts.
return () => {
if (intervalId) {
clearInterval(intervalId);
}
}
}, [setCount]);
return <div></div>;
};
export default App;
You can run the code and see this working below.
const App = () => {
// State to hold count.
const [count, setCount] = React.useState(0);
// Use effect to create and clean up the interval
// (should only run once with current dependencies)
React.useEffect(() => {
// Create interval get the interval ID so it can be cleared later.
const intervalId = setInterval(() => {
// use the function based set state to avoid needing count as a dependency in the useEffect.
// this stops the need to code logic around stoping and recreating the interval.
setCount(currentCount => {
console.log(`count=${currentCount}`);
return currentCount + 1;
});
}, 1000);
// Create function to clean up the interval when the component unmounts.
return () => {
if (intervalId) {
clearInterval(intervalId);
}
}
}, [setCount]);
return <div></div>;
};
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
If you need a more complex implementation as mention in your comment on another answer, you should try using a ref perhaps. For example, this is a custom interval hook I use in my projects. You can see there is an effect that updates callback if it changes.
This ensures you always have the most recent state values and you don't need to use the custom updater function syntax like setCount(count => count + 1).
const useInterval = (callback, delay) => {
const savedCallback = useRef()
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
if (delay !== null) {
const id = setInterval(() => savedCallback.current(), delay)
return () => clearInterval(id)
}
}, [delay])
}
// Usage
const App = () => {
useInterval(() => {
// do something every second
}, 1000)
return (...)
}
This is a very flexible option you could use. However, this hook assumes you want to start your interval when the component mounts. Your code example leads me to believe you want this to start based on the state change of the intervalSet boolean. You could update the custom interval hook, or implement this in your component.
It would look like this in your example:
const useInterval = (callback, delay, initialStart = true) => {
const [start, setStart] = React.useState(initialStart)
const savedCallback = React.useRef()
React.useEffect(() => {
savedCallback.current = callback
}, [callback])
React.useEffect(() => {
if (start && delay !== null) {
const id = setInterval(() => savedCallback.current(), delay)
return () => clearInterval(id)
}
}, [delay, start])
// this function ensures our state is read-only
const startInterval = () => {
setStart(true)
}
return [start, startInterval]
}
const App = () => {
const [countOne, setCountOne] = React.useState(0);
const [countTwo, setCountTwo] = React.useState(0);
const incrementCountOne = () => {
setCountOne(countOne + 1)
}
const incrementCountTwo = () => {
setCountTwo(countTwo + 1)
}
// Starts on component mount by default
useInterval(incrementCountOne, 1000)
// Starts when you call `startIntervalTwo(true)`
const [intervalTwoStarted, startIntervalTwo] = useInterval(incrementCountTwo, 1000, false)
return (
<div>
<p>started: {countOne}</p>
<p>{intervalTwoStarted ? 'started' : <button onClick={startIntervalTwo}>start</button>}: {countTwo}</p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
The problem is the interval is created only once and keeps pointing to the same state value. What I would suggest - move firing the interval to separate useEffect, so it starts when the component mounts. Store interval in a variable so you are able to restart it or clear. Lastly - clear it with every unmount.
const App = () => {
const [count, setCount] = React.useState(0);
const [intervalSet, setIntervalSet] = React.useState(false);
React.useEffect(() => {
setIntervalSet(true);
}, []);
React.useEffect(() => {
const interval = intervalSet ? setInterval(() => {
setCount((c) => {
console.log(c);
return c + 1;
});
}, 1000) : null;
return () => clearInterval(interval);
}, [intervalSet]);
return null;
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

variable in useState not updating in useEffect callback

I'm having an issue while using useState and useEffect hooks
import { useState, useEffect } from "react";
const counter = ({ count, speed }) => {
const [inc, setInc] = useState(0);
useEffect(() => {
const counterInterval = setInterval(() => {
if(inc < count){
setInc(inc + 1);
}else{
clearInterval(counterInterval);
}
}, speed);
}, [count]);
return inc;
}
export default counter;
Above code is a counter component, it takes count in props, then initializes inc with 0 and increments it till it becomes equal to count
The issue is I'm not getting the updated value of inc in useEffect's and setInterval's callback every time I'm getting 0, so it renders inc as 1 and setInterval never get clear. I think inc must be in closure of use useEffect's and setInterval's callback so I must get the update inc there, So maybe it's a bug?
I can't pass inc in dependency ( which is suggested in other similar questions ) because in my case, I've setInterval in useEffect so passing inc in dependency array is causing an infinite loop
I have a working solution using a stateful component, but I want to achieve this using functional component
There are a couple of issues:
You're not returning a function from useEffect to clear the interval
Your inc value is out of sync because you're not using the previous value of inc.
One option:
const counter = ({ count, speed }) => {
const [inc, setInc] = useState(0);
useEffect(() => {
const counterInterval = setInterval(() => {
setInc(inc => {
if(inc < count){
return inc + 1;
}else{
// Make sure to clear the interval in the else case, or
// it will keep running (even though you don't see it)
clearInterval(counterInterval);
return inc;
}
});
}, speed);
// Clear the interval every time `useEffect` runs
return () => clearInterval(counterInterval);
}, [count, speed]);
return inc;
}
Another option is to include inc in the deps array, this makes things simpler since you don't need to use the previous inc inside setInc:
const counter = ({ count, speed }) => {
const [inc, setInc] = useState(0);
useEffect(() => {
const counterInterval = setInterval(() => {
if(inc < count){
return setInc(inc + 1);
}else{
// Make sure to clear your interval in the else case,
// or it will keep running (even though you don't see it)
clearInterval(counterInterval);
}
}, speed);
// Clear the interval every time `useEffect` runs
return () => clearInterval(counterInterval);
}, [count, speed, inc]);
return inc;
}
There's even a third way that's even simpler:
Include inc in the deps array and if inc >= count, return early before calling setInterval:
const [inc, setInc] = useState(0);
useEffect(() => {
if (inc >= count) return;
const counterInterval = setInterval(() => {
setInc(inc + 1);
}, speed);
return () => clearInterval(counterInterval);
}, [count, speed, inc]);
return inc;
The issue here is that the callback from clearInterval is defined every time useEffect runs, which is when count updates. The value inc had when defined is the one that will be read in the callback.
This edit has a different approach. We include a ref to keep track of inc being less than count, if it is less we can continue incrementing inc. If it is not, then we clear the counter (as you had in the question). Every time inc updates, we evaluate if it is still lesser than count and save it in the ref. This value is then used in the previous useEffect.
I included a dependency to speed as #DennisVash correctly indicates in his answer.
const useCounter = ({ count, speed }) => {
const [inc, setInc] = useState(0);
const inc_lt_count = useRef(inc < count);
useEffect(() => {
const counterInterval = setInterval(() => {
if (inc_lt_count.current) {
setInc(inc => inc + 1);
} else {
clearInterval(counterInterval);
}
}, speed);
return () => clearInterval(counterInterval);
}, [count, speed]);
useEffect(() => {
if (inc < count) {
inc_lt_count.current = true;
} else {
inc_lt_count.current = false;
}
}, [inc, count]);
return inc;
};
The main problems that need to be dealt with are Closures and clearing interval on a condition which depends on props.
You should add the conditional check within the functional setState:
setInc(inc => (inc < count ? inc + 1 : inc));
Also, the clearing interval should happen on unmount.
If you want to add clearInterval on condition (inc < count), you need to save references for the interval id and the increased number:
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const useCounter = ({ count, speed }) => {
const [inc, setInc] = useState(0);
const incRef = useRef(inc);
const idRef = useRef();
useEffect(() => {
idRef.current = setInterval(() => {
setInc(inc => (inc < count ? inc + 1 : inc));
incRef.current++;
}, speed);
return () => clearInterval(idRef.current);
}, [count, speed]);
useEffect(() => {
if (incRef.current > count) {
clearInterval(idRef.current);
}
}, [count]);
useEffect(() => {
console.log(incRef.current);
});
return inc;
};
const App = () => {
const inc = useCounter({ count: 10, speed: 1000 });
return <h1>Counter : {inc}</h1>;
};
ReactDOM.render(<App />, document.getElementById('root'));

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

Why setInterval with useState hook doesn't update state properly [duplicate]

I'm trying out the new React Hooks and have a Clock component with a time value which is supposed to increase every second. However, the value does not increase beyond one.
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
The reason is because the callback passed into setInterval's closure only accesses the time variable in the first render, it doesn't have access to the new time value in the subsequent render because the useEffect() is not invoked the second time.
time always has the value of 0 within the setInterval callback.
Like the setState you are familiar with, state hooks have two forms: one where it takes in the updated state, and the callback form which the current state is passed in. You should use the second form and read the latest state value within the setState callback to ensure that you have the latest state value before incrementing it.
Bonus: Alternative Approaches
Dan Abramov goes in-depth into the topic about using setInterval with hooks in his blog post and provides alternative ways around this issue. Highly recommend reading it!
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(prevTime => prevTime + 1); // <-- Change this line!
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
As others have pointed out, the problem is that useState is only called once (as deps = []) to set up the interval:
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => window.clearInterval(timer);
}, []);
Then, every time setInterval ticks, it will actually call setTime(time + 1), but time will always hold the value it had initially when the setInterval callback (closure) was defined.
You can use the alternative form of useState's setter and provide a callback rather than the actual value you want to set (just like with setState):
setTime(prevTime => prevTime + 1);
But I would encourage you to create your own useInterval hook so that you can DRY and simplify your code by using setInterval declaratively, as Dan Abramov suggests here in Making setInterval Declarative with React Hooks:
function useInterval(callback, delay) {
const intervalRef = React.useRef();
const callbackRef = React.useRef(callback);
// Remember the latest callback:
//
// Without this, if you change the callback, when setInterval ticks again, it
// will still call your old callback.
//
// If you add `callback` to useEffect's deps, it will work fine but the
// interval will be reset.
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Set up the interval:
React.useEffect(() => {
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(() => callbackRef.current(), delay);
// Clear interval if the components is unmounted or the delay changes:
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
// Returns a ref to the interval ID in case you want to clear it manually:
return intervalRef;
}
const Clock = () => {
const [time, setTime] = React.useState(0);
const [isPaused, setPaused] = React.useState(false);
const intervalRef = useInterval(() => {
if (time < 10) {
setTime(time + 1);
} else {
window.clearInterval(intervalRef.current);
}
}, isPaused ? null : 1000);
return (<React.Fragment>
<button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }>
{ isPaused ? 'RESUME ⏳' : 'PAUSE 🚧' }
</button>
<p>{ time.toString().padStart(2, '0') }/10 sec.</p>
<p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p>
</React.Fragment>);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
p + p {
margin-top: 8px;
}
#app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
button {
margin: 32px 0;
padding: 8px;
border: 2px solid black;
background: transparent;
cursor: pointer;
border-radius: 2px;
}
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
Apart from producing simpler and cleaner code, this allows you to pause (and clear) the interval automatically by simply passing delay = null and also returns the interval ID, in case you want to cancel it yourself manually (that's not covered in Dan's posts).
Actually, this could also be improved so that it doesn't restart the delay when unpaused, but I guess for most uses cases this is good enough.
If you are looking for a similar answer for setTimeout rather than setInterval, check this out: https://stackoverflow.com/a/59274757/3723993.
You can also find declarative version of setTimeout and setInterval, useTimeout and useInterval, a few additional hooks written in TypeScript in https://www.npmjs.com/package/#swyg/corre.
useEffect function is evaluated only once on component mount when empty input list is provided.
An alternative to setInterval is to set new interval with setTimeout each time the state is updated:
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => {
setTime(time + 1);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [time]);
The performance impact of setTimeout is insignificant and can be generally ignored. Unless the component is time-sensitive to the point where newly set timeouts cause undesirable effects, both setInterval and setTimeout approaches are acceptable.
useRef can solve this problem, here is a similar component which increase the counter in every 1000ms
import { useState, useEffect, useRef } from "react";
export default function App() {
const initalState = 0;
const [count, setCount] = useState(initalState);
const counterRef = useRef(initalState);
useEffect(() => {
counterRef.current = count;
})
useEffect(() => {
setInterval(() => {
setCount(counterRef.current + 1);
}, 1000);
}, []);
return (
<div className="App">
<h1>The current count is:</h1>
<h2>{count}</h2>
</div>
);
}
and i think this article will help you about using interval for react hooks
An alternative solution would be to use useReducer, as it will always be passed the current state.
function Clock() {
const [time, dispatch] = React.useReducer((state = 0, action) => {
if (action.type === 'add') return state + 1
return state
});
React.useEffect(() => {
const timer = window.setInterval(() => {
dispatch({ type: 'add' });
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((seconds) => {
if (seconds === 5) {
setSeconds(0);
return clearInterval(interval);
}
return (seconds += 1);
});
}, 1000);
}, []);
Note: This will help to update and reset the counter with useState hook. seconds will stop after 5 seconds. Because first change setSecond value then stop timer with updated seconds within setInterval. as useEffect run once.
This solutions dont work for me because i need to get the variable and do some stuff not just update it.
I get a workaround to get the updated value of the hook with a promise
Eg:
async function getCurrentHookValue(setHookFunction) {
return new Promise((resolve) => {
setHookFunction(prev => {
resolve(prev)
return prev;
})
})
}
With this i can get the value inside the setInterval function like this
let dateFrom = await getCurrentHackValue(setSelectedDateFrom);
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time => time + 1);// **set callback function here**
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
Somehow similar issue, but when working with a state value which is an Object and is not updating.
I had some issue with that so I hope this may help someone.
We need to pass the older object merged with the new one
const [data, setData] = useState({key1: "val", key2: "val"});
useEffect(() => {
setData(...data, {key2: "new val", newKey: "another new"}); // --> Pass old object
}, []);
Do as below it works fine.
const [count , setCount] = useState(0);
async function increment(count,value) {
await setCount(count => count + 1);
}
//call increment function
increment(count);
I copied the code from this blog. All credits to the owner. https://overreacted.io/making-setinterval-declarative-with-react-hooks/
The only thing is that I adapted this React code to React Native code so if you are a react native coder just copy this and adapt it to what you want. Is very easy to adapt it!
import React, {useState, useEffect, useRef} from "react";
import {Text} from 'react-native';
function Counter() {
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]);
}
const [count, setCount] = useState(0);
useInterval(() => {
// Your custom logic here
setCount(count + 1);
}, 1000);
return <Text>{count}</Text>;
}
export default Counter;
const [loop, setLoop] = useState(0);
useEffect(() => {
setInterval(() => setLoop(Math.random()), 5000);
}, []);
useEffect(() => {
// DO SOMETHING...
}, [loop])
For those looking for a minimalist solution for:
Stop interval after N seconds, and
Be able to reset it multiple times again on button click.
(I am not a React expert by any means my coworker asked to help out, I wrote this up and thought someone else might find it useful.)
const [disabled, setDisabled] = useState(true)
const [inter, setInter] = useState(null)
const [seconds, setSeconds] = useState(0)
const startCounting = () => {
setSeconds(0)
setDisabled(true)
setInter(window.setInterval(() => {
setSeconds(seconds => seconds + 1)
}, 1000))
}
useEffect(() => {
startCounting()
}, [])
useEffect(() => {
if (seconds >= 3) {
setDisabled(false)
clearInterval(inter)
}
}, [seconds])
return (<button style = {{fontSize:'64px'}}
onClick={startCounting}
disabled = {disabled}>{seconds}</button>)
}
Tell React re-render when time changed.opt out
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, [time]);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

Categories

Resources