The timeout does not clear all instances in React JS

Viewed 56

I have the next code where i created a hook to handle the timeout function. The idea is, when the user will click on a button, the message should appear and to be cleared after an amount of time.

import "./styles.css";
import React, { useRef, useCallback, useState, useEffect } from "react";
const useTimer = () => {
  const timer = useRef();
  const fn = useCallback((callback, timeout = 0) => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      callback();
    }, timeout);
  }, []);

  const clearTimeoutHandler = () => {
    console.log("clear");
    return clearTimeout(timer.current);
  };

  useEffect(() => {
    return clearTimeoutHandler();
  }, []);

  return { fn, clearTimeoutHandler };
};

export default function App() {
  const [state, setState] = useState(false);
  const [list, updateList] = useState([]);
  const timer = useTimer();

  const removeItem = (id) => {
    updateList((list) => list.filter((x) => x.id !== id));
    timer.clearTimeoutHandler();
  };

  const onClickHandler = () => {
    const id = new Date();

    const element = {
      id: id,
      text: "text" + new Date()
    };

    updateList(list.concat([{ ...list, ...element }]));

    timer.fn(() => {
      removeItem(id);
    }, 2000);
  };

  const bottomMess = () => {
    setState({ m: "hi" });
    timer.fn(
      () => {
        setState(undefined);
      },
      2000
    );
  };

  return (
    <div className="App">
      <button onClick={bottomMess}>Open bottom message</button>
      <button onClick={onClickHandler}>Open top message</button>
      {list.map((d) => (
        <h1>{d.text}</h1>
      ))}
      {state && (
        <div style={{ position: "absolute", bottom: 0 }}>
          bottom message {state.m}
        </div>
      )}
    </div>
  );
}

ISSUE: when I click on Open top message button 3 times, only the last message disappears, but I expect all to be removed.
Where is the issue in the hook and how to make the hook workable?
demo: https://codesandbox.io/s/summer-fire-2xw0d5?file=/src/App.js

1 Answers

Your current implementation only keeps track of the last timer. You can return timerId from fn and accept timerId as an argument to clearTimeoutHandler function:

Try like this:

const useTimer = () => {
  const timer = useRef([]);
  const fn = useCallback((callback, timeout = 0) => {
    const timerId = setTimeout(() => {
      callback();
    }, timeout);
    timer.current.push(timerId);
    return timerId;
  }, []);

  const clearTimeoutHandler = (timerId) => {
    return clearTimeout(timerId);
  };

  const clearAll = () => {
    timer.current.forEach((timerId) => clearTimeout(timerId));
  };

  useEffect(() => {
    return clearAll;
  }, []);

  return { fn, clearTimeoutHandler, clearAll };
};

Edit aged-voice-tjwtso

Related