Skip to content

useHotkey reads target once and never re-reads it. useHotkeys re-reads it every render, so the two hooks behave differently for the same option. #147

Description

@erictheswift

TanStack Hotkeys version

v0.10.0 (also v0.9.1)

Framework/Library version

React v19.2.5

Describe the bug and the steps to reproduce it

Two ways this bites:

A ref that's still null when the effect first runs never registers, even after it attaches:

function Component({ ready }) {
  const ref = useRef(null)
  useHotkey('Mod+S', callback, { target: ref })

  if (!ready) return <Spinner />          // effect runs here, ref.current is null
  return <div ref={ref} tabIndex={-1} />  // too late
}

And a target that changes after mount keeps the listener on the old element — the hotkey fires on the element the ref no longer points at, and not on the new one. Same with a plain element target, not just refs.

There's a targetChanged branch that looks like it covers the second one, but it's inside the [hotkeyString] effect, so it only runs if the hotkey string changes too. useHotkeys has a passing test for this exact case (tests/useHotkeys.test.tsx:235, "should move a registration when only the target changes") — the same test against useHotkey fails.

Failing tests, branched off 4f59e18:
https://github.com/erictheswift/tanstack-hotkeys/tree/repro/usehotkey-target-not-reresolved

git fetch https://github.com/erictheswift/tanstack-hotkeys repro/usehotkey-target-not-reresolved && git checkout FETCH_HEAD
pnpm --filter @tanstack/react-hotkeys exec vitest run tests/useHotkey-late-target.test.tsx
 ❯ tests/useHotkey-late-target.test.tsx (5 tests | 3 failed)
   × registers once the ref is attached
   × moves the registration when a ref points at a different element
   × moves the registration when a plain element target changes
   ✓ registers when the element is present from the first render
   ✓ useHotkeys handles the same case

What made this hard to find: the existing test at tests/useHotkey.test.tsx:237 is called "should wait for ref to be attached", but it only checks the hotkey doesn't fire while the ref is null — it never attaches the ref and checks it starts working. The name reads like deferred registration is supported.

Two things I'm unsure about:

Is the difference from useHotkeys intentional? Both take the same target option and the docs describe it the same way.

Could it warn in dev? Both cases are silent — the hotkey just doesn't fire, or fires on the wrong element. A null ref on first run is legitimately transient so I don't know what the trigger would be, but the silence is what cost the time.

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

https://github.com/erictheswift/tanstack-hotkeys/tree/repro/usehotkey-target-not-reresolved One test file on top of 4f59e18, nothing else.

Screenshots or Videos (Optional)

No response

Do you intend to try to help solve this bug with your own PR?

Maybe, I'll investigate and start debugging

Terms & Code of Conduct

  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

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