Skip to content

useDebouncedEvent: pending calls can't be cancelled, and survive unmount #11399

Closed

Description

@fzaninotto

What you were expecting

A debounced callback from useDebouncedEvent can be cancelled (or flushed),
and a pending call does not run after the component unmounts.

What happened instead

useDebouncedEvent wraps lodash/debounce but hides its cancel() and
flush(), and never cancels the debounced instance:

  • No cancel/flush. The returned callback is a plain function. A component
    that needs to drop a pending call (e.g. a dialog that resets its state when
    it closes) cannot, and must work around it by calling the debounced
    function again with a neutral value, so that the trailing call carries that
    value instead.
  • Pending call survives unmount. The useLayoutEffect that creates the
    debounced function has no cleanup, so a call pending when the component
    unmounts still fires delay ms later, running the callback (often a state
    setter or a data provider call) on an unmounted component.
  • Pending call survives a delay change. When delay changes, a new
    debounced instance replaces the old one, but the old one's pending call is
    not cancelled and still fires.

Steps to reproduce

  1. Render a component that calls const update = useDebouncedEvent(setValue, 300)
    on input change, and resets value when a "Close" button is clicked.
  2. Type in the input, then click "Close" within 300 ms.
  3. 300 ms later, setValue runs with the typed text, undoing the reset.

Unmounting the component within 300 ms of typing shows the second problem:
the callback still runs after unmount.

Proposed fix

useLayoutEffect(() => {
    const debounced = debounce(stableCallback, delay);
    debouncedCallbackRef.current = debounced;
    return () => debounced.cancel();
}, [stableCallback, delay]);

Activity

  1. slax57 commented on Oct 12, 2026

    @slax57
    Contributor

    @fzaninotto Worth mentioning that RA EE's useDebouncedEvent does expose cancel() and flush(), although I do not believe it cancels on unmount (which can be a good thing for some use-cases IMHO, e.g. auto-saving).

  2. fzaninotto commented on Oct 16, 2026

    @fzaninotto
    MemberAuthor

    #11400 implements another strategy: flush on onmount instead of cancel, which is better in the use cases that we consider (e.g., AutoSave).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions