`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.
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 719
- Forks
- 47
- Avg merge
- 5d 5h
- Merged PRs (30d)
- 1
Description
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with tests/useHotkey-late-target.test.tsx on the repro branch and compare its cases with tests/useHotkeys.test.tsx:235 and tests/useHotkey.test.tsx:237. Trace the useHotkey targetChanged branch mentioned in the issue and run pnpm --filter @tanstack/react-hotkeys exec vitest run tests/useHotkey-late-target.test.tsx. Done means all five cases pass, including late ref attachment and target changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100