Try @eslint-react/kit@beta
logoESLint React

no-leaked-timeout

Enforces that every 'setTimeout' in a component or custom hook has a corresponding 'clearTimeout'.

Full Name in eslint-plugin-react-web-api

react-web-api/no-leaked-timeout

Full Name in @eslint-react/eslint-plugin

@eslint-react/web-api-no-leaked-timeout

Presets

web-api recommended recommended-typescript recommended-type-checked strict strict-typescript strict-type-checked

Rule Details

Scheduling a timeout within the setup function of useEffect without canceling it in the cleanup function can lead to unwanted setTimeout callback executions and may also result in stale values captured by previous renders' effects after each subsequent re-render.

Examples

Setting timeout without cleanup in useEffect

import React, { useEffect, useState } from "react";

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Problem: A 'setTimeout' created in 'useEffect' must be cleared in the cleanup function.
    const timeoutId = setTimeout(() => console.log(count), 1000);
  }, []);

  return null;
}

Clearing timeout in useEffect cleanup

import React, { useEffect, useState } from "react";

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Recommended: Store the timeout ID
    const timeoutId = setTimeout(() => console.log(count), 1000);
    // Recommended: Clear the timeout in the cleanup function
    return () => clearTimeout(timeoutId);
  }, []);

  return null;
}

Canceling a timeout when dependencies change

When a timeout depends on reactive values like props or state, clear the previous timeout in the cleanup function before scheduling a new one. This prevents outdated timeouts from firing after the value has already changed.

// Recommended: clear the previous timeout before scheduling a new one
import { useState, useEffect } from "react";

function Playground() {
  const [text, setText] = useState("a");

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      console.log("⏰ " + text);
    }, 3000);

    return () => {
      clearTimeout(timeoutId);
    };
  }, [text]);

  return (
    <>
      <label>
        What to log:{" "}
        <input
          value={text}
          onChange={(e) => setText(e.target.value)}
        />
      </label>
      <h1>{text}</h1>
    </>
  );
}

Versions

Resources

Further Reading


See Also

On this page