John Winston
John Winston

Reputation: 1471

How to cleanup setTimeout/setInterval in event handler in React?

How can I clean up function like setTimeout or setInterval in event handler in React? Or is this unnecessary to do so?

import React from 'react'

function App(){
  return (
    <button onClick={() => {
      setTimeout(() => {
        console.log('you have clicked me')
        //How to clean this up?
      }, 500)
    }}>Click me</button>
  )
}

export default App

Upvotes: 3

Views: 2397

Answers (3)

Yu Miao
Yu Miao

Reputation: 572

Clear timer when unmount component

import React from 'react'

function App(){
  const timerRef = React.useRef(null)
  React.useEffect(() => {
    return () => {
     // clean
      timerRef.target && clearTimeout(timerRef.target)
    }
  },[])
  return (
    <button onClick={() => {
      timerRef.target = setTimeout(() => {
        console.log('you have clicked me')
      }, 500)
    }}>Click me</button>
  )
}

export default App

Upvotes: -1

Dmitriy Mozgovoy
Dmitriy Mozgovoy

Reputation: 1597

One more solution (Live Demo):

import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";

export default function TestComponent(props) {
  const [text, setText] = useState("");

  const click = useAsyncCallback(function* (ms) {
    yield CPromise.delay(ms);
    setText("done!" + new Date().toLocaleTimeString());
  }, []);

  return (
    <div className="component">
      <div className="caption">useAsyncEffect demo:</div>
      <div>{text}</div>
      <button onClick={() => click(2000)}>Click me!</button>
      <button onClick={click.cancel}>Cancel scheduled task</button>
    </div>
  );
}

In case if you want to cancel the previous pending task (Live demo):

import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";

export default function TestComponent(props) {
  const [text, setText] = useState("");

  const click = useAsyncCallback(
    function* (ms) {
      console.log("click");
      yield CPromise.delay(ms);
      setText("done!" + new Date().toLocaleTimeString());
    },
    { deps: [], cancelPrevios: true }
  );

  return (
    <div className="component">
      <div className="caption">useAsyncEffect demo:</div>
      <div>{text}</div>
      <button onClick={() => click(5000)}>Click me!</button>
      <button onClick={click.cancel}>Cancel scheduled task</button>
    </div>
  );
}

Upvotes: -1

T.J. Crowder
T.J. Crowder

Reputation: 1075317

Whether it's necessary depends on what the callback does, but certainly if the component is unmounted it almost doesn't matter what it does, you do need to cancel the timer / clear the interval.

To do that in a function component like yours, you use a useEffect cleanup function with an empty dependency array. You probably want to store the timer handle in a ref.

(FWIW, I'd also define the function outside of the onClick attribute, just for clarity.)

import React, {useEffect, useRef} from 'react';

function App() {
    const instance = useRef({timer: 0});

    useEffect(() => {
        // What you return is the cleanup function
        return () => {
            clearTimeout(instance.current.timer);
        };
    }, []);

    const onClick = () => {
        // Clear any previous one (it's fine if it's `0`,
        // `clearTimeout` won't do anything)
        clearTimeout(instance.current.timer);
        // Set the timeout and remember the value on the object
        instance.current.timer = setTimeout(() => {
            console.log('you have clicked me')
            //How to clean this up?
        }, 500);
    };

    return (
        <button onClick={onClick}>Click me</button>
    )
}

export default App;

An object you store as a ref is usually a useful place to put things you would otherwise have put on this in a class component.

(If you want to avoid re-rendering button when other state in your component changes (right now there's no other state, so no need), you could use useCallback for onClick so button always sees the same function.)

Upvotes: 3

Related Questions