React hooks - right way to clear timeouts and intervals
Defined return () => { /*code/* }
function inside useEffect
runs every time useEffect
runs (except first render on component mount) and on component unmount (if you don't display component any more).
This is a working way to use and clear timeouts or intervals:
Sandbox example.
import { useState, useEffect } from "react";
const delay = 5;
export default function App() {
const [show, setShow] = useState(false);
useEffect(
() => {
let timer1 = setTimeout(() => setShow(true), delay * 1000);
// this will clear Timeout
// when component unmount like in willComponentUnmount
// and show will not change to true
return () => {
clearTimeout(timer1);
};
},
// useEffect will run only one time with empty []
// if you pass a value to array,
// like this - [data]
// than clearTimeout will run every time
// this value changes (useEffect re-run)
[]
);
return show ? (
<div>show is true, {delay}seconds passed</div>
) : (
<div>show is false, wait {delay}seconds</div>
);
}
If you need to clear timeouts or intervals in another component:
Sandbox example.
import { useState, useEffect, useRef } from "react";
const delay = 1;
export default function App() {
const [counter, setCounter] = useState(0);
const timer = useRef(null); // we can save timer in useRef and pass it to child
useEffect(() => {
// useRef value stored in .current property
timer.current = setInterval(() => setCounter((v) => v + 1), delay * 1000);
// clear on component unmount
return () => {
clearInterval(timer.current);
};
}, []);
return (
<div>
<div>Interval is working, counter is: {counter}</div>
<Child counter={counter} currentTimer={timer.current} />
</div>
);
}
function Child({ counter, currentTimer }) {
// this will clearInterval in parent component after counter gets to 5
useEffect(() => {
if (counter < 5) return;
clearInterval(currentTimer);
}, [counter, currentTimer]);
return null;
}
Article from Dan Abramov.
Does clearing timeout/interval have to be inside `useEffect` react hook?
You must be sure to clear all intervals before your component gets unmounted.
Intervals never disappear automatically when components get unmounted and to clear them, clearInterval
is often called inside useEffect(() => {}, []).
The function retured in useEffect(() => {}, []) gets called when the compoment is unmounted.
return () => {
clearInterval(id.current)
}
You can see that intervals set inside a component never disappears automatically by checking this sandbox link. https://codesandbox.io/s/cool-water-oij8s
Intervals remain forever unless clearInterval
is called.
React useEffect hook doesn't clear interval
According to the React docs:
This is why React also cleans up effects from the previous render before running the effects next time.
Based on this, I believe that the cleanup function is not run until the next iteration of the useEffect
. In your example, after timeout
hits 0, the useEffect
will run 1 more time, setting up a new interval, but because timeout
is 0, it will not update the state, so the useEffect
won't be called on the next render. That means the cleanup function never gets called for that last iteration.
how do I clearInterval on-click, with React Hooks?
--- added @ 2019-02-11 15:58 ---
A good pattern to use setInterval
with Hooks API:
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
--- origin answer ---
Some issues:
Do not use non-constant variables in the global scope of any modules. If you use two instances of this module in one page, they’ll share those global variables.
There’s no need to clear timer in the “else” branch because if the
timerOn
change from true to false, the return function will be executed.
A better way in my thoughts:
import { useState, useEffect } from 'react';
export default (handler, interval) => {
const [intervalId, setIntervalId] = useState();
useEffect(() => {
const id = setInterval(handler, interval);
setIntervalId(id);
return () => clearInterval(id);
}, []);
return () => clearInterval(intervalId);
};
Running example here:
https://codesandbox.io/embed/52o442wq8l?codemirror=1
Is useRef Hook a must to set and clear intervals in React?
As stated at the docs you shared;
If we just wanted to set an interval, we wouldn’t need the ref (id could be local to the effect).
useEffect(() => {
const id = setInterval(() => {
setCounter(prev => prev + 1);
}, 1000);
return () => {
clearInterval(id);
};
});
but it’s useful if we want to clear the interval from an event handler:
// ...
function handleCancelClick() {
clearInterval(intervalRef.current);
}
// ...
React hooks - Set a timeout inside useEffect but be able to clear it from a mouse event?
Whenever your component re-renders, timer
variable will be re-declared with an initial value of null
. As a result, when useEffect
hook executes whenever any of its dependency changes, timer
variable is null
.
You can solve the problem by making sure that value of the timer
variable is persisted across re-renders of your component. To persist the value across re-renders, either save the id of the setTimeout
using useRef
hook or save it in the state, i.e. useState
hook.
Related Topics
Prevent Text Selection After Double Click
How to Match Multiple Occurrences with a Regex in JavaScript Similar to PHP's Preg_Match_All()
Is This Simple String Considered Valid JSON
Http Get Request in Node.Js Express
Has Facebook Sharer.PHP Changed to No Longer Accept Detailed Parameters
Jquery VS Document.Queryselectorall
Removing Page Title and Date When Printing Web Page (With CSS)
Get Value of a Custom Attribute
Sending Message to a Specific Connected Users Using Websocket
How to Remove Youtube Branding After Embedding Video in Web Page
How to Inherit from a Class in JavaScript
Correct Use of Arrow Functions in React
Date VS New Date in JavaScript
Optimum Way to Compare Strings in JavaScript
Encrypt in PHP Openssl and Decrypt in JavaScript Cryptojs
Scrolltop Animation Without Jquery
Howto: Div with Onclick Inside Another Div with Onclick JavaScript
How to Change HTML Object Element Data Attribute Value in JavaScript