Skip to content

useDelayState throws error in vitest #805

Description

@anotheri

The problem: useDelayState schedules a delayed setValue via setTimeout / raf and defines a cancelPending callback, but cancelPending is only ever called from setDelayValue. There is no cleanup effect, so a component that unmounts with a pending update leaves the timer running and setValue fires against an unmounted tree.

In a browser that is not a problem, but under a jsdom test runner it throws an error. Vitest tears the jsdom environment down when a test file finishes; setTimeout on globalThis is Node's (vitest does not shadow it), so the timer survives, fires with window already deleted, and React DOM throws while resolving update priority — failing the whole run and blaming whichever test file happened to be running:

ReferenceError: window is not defined
   resolveUpdatePriority  react-dom/cjs/react-dom-client.development.js:1308:7
   requestUpdateLane      react-dom-client.development.js:16345:11
   dispatchSetState       react-dom-client.development.js:9126:14
   Timeout._onTimeout     @rc-component/util/es/hooks/useDelayState.js:30:51
   listOnTimeout          node:internal/timers:635:17

Unmounting alone is not enough, React still routes dispatchSetState through requestUpdateLane, so the timer genuinely has to be cancelled.

This is easy to hit through antd 6.6: form/hooks/useDebounce calls setCacheValue(value, { ms: value.length ? 0 : 10 }) on mount, so every Form.Item leaves a pending timer; Button with an object loading and Slider / Typography / Upload do the same.

Deps: @rc-component/util@1.12.0, antd@6.6.0, react@19.2.8, react-dom@19.2.8, vitest@4.1.10, jsdom@30.0.1.

Metadata

Metadata

Assignees

No one assigned

    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