Skip to content

Compose: Fix useDebounce losing calls when the debounced function changes - #84192

Open
Mustafabharmal wants to merge 3 commits into
WordPress:trunkfrom
Mustafabharmal:fix/use-debounce-lost-calls
Open

Mustafabharmal wants to merge 3 commits into
WordPress:trunkfrom
Mustafabharmal:fix/use-debounce-lost-calls

Conversation

@Mustafabharmal

Copy link
Copy Markdown
Contributor

What?

Fixes #35505.

useDebounce no longer cancels scheduled calls when the function passed to it changes. The latest version of the function is called instead.

Why?

useDebounce recreated the debounced function, and cancelled the pending call, whenever fn changed. A common pattern (a useCallback whose dependencies change after the debounced function is called) meant the call was silently dropped and never ran. Components passing inline functions (e.g. PaletteEditListView) can have a pending call cancelled whenever they re-render within the wait time.

Some callers already work around this by wrapping fn in useEvent (useMarkPersistent, media-fields). This moves that fix into the hook so every caller benefits.

How?

fn is wrapped with useEvent, so the debounced function stays stable across renders but always invokes the latest fn. Scheduled calls are still cancelled when wait/options change or on unmount. The docblock/README no longer asks callers to wrap fn in useCallback.

Unit tests are added for useDebounce (it previously had none).

Testing Instructions

  1. Open the post editor.
  2. Open the browser console and paste the snippet below.
  3. On trunk: three New state from interval logs appear, then nothing. The debounced callback never runs.
  4. On this branch: New state from debounce is logged ~3 seconds after the last interval tick.
  5. Run npm run test:unit:vitest -- packages/compose/src/hooks/use-debounce.

Hard-reload (Cmd/Ctrl+Shift+R) between runs, since registerPlugin can't register the same name twice.

Reproduction snippet
var { useCallback, useEffect, useState, useRef } = wp.element;
var { useDebounce } = wp.compose;
var { registerPlugin } = wp.plugins;

function DebounceTest() {
    const [ state, setState ] = useState( 0 );
    const ticks = useRef( 0 );

    const updateStateCallback = useCallback( () => {
            console.log( 'New state from debounce:', state + 1 );
            setState( state + 1 );
    }, [ state ] );

    const updateStateDebounce = useDebounce( updateStateCallback, 3000 );

    useEffect( () => {
            const interval = setInterval( () => {
                    if ( ++ticks.current > 3 ) {
                            return;
                    }
                    const newState = Math.random();
                    console.log( 'New state from interval:', newState );
                    updateStateDebounce();
                    setState( newState );
            }, 1000 );
            return () => clearInterval( interval );
    }, [ updateStateDebounce ] );

    return null;
}

registerPlugin( 'debounce-test-plugin', { render: DebounceTest } );

Note: the original snippet in the issue calls the debounced function every 1s with a 3s wait, so it never fires with any debounce implementation (lodash included) while the interval keeps running. The snippet above stops the interval after 3 ticks so the debounce has time to fire.

Screenshots or screencast

Before After
image image

Use of AI Tools

I used Claude Code to look into the cause and draft the change and this description. I reviewed and tested the change myself.

@github-actions github-actions Bot added the [Package] Compose /packages/compose label Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

🎉 Props

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @ciobanu0151.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: ciobanu0151.

Co-authored-by: Mustafabharmal <mustafabharmal@git.wordpress.org>
Co-authored-by: talldan <talldanwp@git.wordpress.org>
Co-authored-by: Thelmachido <thelmachido@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

🏷️ Labels

This pull request needs exactly one label indicating its type, and has 0.

  • Required: any label starting with [Type].
  • Found: none.

Read more about Type labels in Gutenberg. If you cannot add labels yourself, a reviewer can do it for you.

This branch has not been deployed

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

Labels

[Package] Compose /packages/compose

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useDebounce hook loses function calls if the dependency changes

1 participant