Skip to content

fix: Add guard to normalizeKeyName()#98

Merged
KevinVandy merged 5 commits intoTanStack:mainfrom
ryck:feat-add-guard-normalizeKeyName
Mar 31, 2026
Merged

fix: Add guard to normalizeKeyName()#98
KevinVandy merged 5 commits intoTanStack:mainfrom
ryck:feat-add-guard-normalizeKeyName

Conversation

@ryck
Copy link
Copy Markdown
Contributor

@ryck ryck commented Mar 31, 2026

🎯 Changes

normalizeKeyName calls key.toUpperCase() without guarding against undefined, and event.key can be undefined in rare cases (browser extensions synthesizing key events, accessibility tools, certain OS/browser combinations).

This PR adds a guard for these cases, so no more errors like:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'payload')

Fixes #97

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes

    • Hotkeys input handling now treats empty or undefined key values as empty, preventing unexpected behavior when key data is missing.
  • Tests

    • Added tests for the falsy-input edge case in hotkeys normalization to ensure consistent, stable behavior.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

normalizeKeyName now immediately returns an empty string for falsy inputs (e.g., undefined or empty string). A test was added asserting this guard behavior; a changeset notes the patch.

Changes

Cohort / File(s) Summary
Guard clause for falsy inputs
packages/hotkeys/src/constants.ts
Added early return when key is falsy so normalizeKeyName returns '' instead of calling toUpperCase() on undefined.
Test coverage for guard clause
packages/hotkeys/tests/parse.test.ts
Added tests asserting normalizeKeyName('') and normalizeKeyName(String(undefined)) (falsy inputs) return an empty string.
Release note
.changeset/early-candles-warn.md
Added a changeset entry for a patch release documenting the null-check fix in normalizeKeyName.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A tiny guard hops in, snug and keen,
Empty keys are caught before they've been seen.
No crashes, no fuss, just a gentle fix,
Hotkeys keep hopping through code and tricks.
✨🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description follows the template, explains the problem and solution clearly, references the fixed issue, and shows both checklist items completed and changeset checkbox unchecked.
Linked Issues check ✅ Passed The PR fully addresses issue #97 by adding a guard to normalizeKeyName() to handle undefined values, preventing the TypeError that occurs when event.key is undefined.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the normalizeKeyName guard issue: code fix, corresponding test, and a changeset entry.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title accurately and concisely describes the main change: adding a guard condition to the normalizeKeyName() function to handle falsy inputs, which is the core purpose of this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/hotkeys/src/constants.ts`:
- Around line 458-460: Fix the typo in the inline comment that currently reads
"// key an be undefined in rare cases" to "// key can be undefined in rare
cases" near the null-check for the variable 'key' (the line with "if (!key)
return ''"); update only the comment text to correct "an" to "can".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0a4dbe1a-1b12-4d8a-86c2-490f5a6f4b29

📥 Commits

Reviewing files that changed from the base of the PR and between 1f30fee and f04822e.

📒 Files selected for processing (2)
  • packages/hotkeys/src/constants.ts
  • packages/hotkeys/tests/parse.test.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/hotkeys/src/constants.ts`:
- Around line 458-460: The guard in constants.ts currently returns an empty
string for an invalid/undefined key which lets invalid keys flow downstream;
change the sentinel to return null instead of '' so callers must short-circuit
and handle the absence of a key. Update the function that checks `key` (the if
(!key) return ...) to return null, and then update any callers that consume its
result—particularly code paths that add to `#heldKeysSet` and `#heldCodesMap` in
key-state-tracker.ts—to explicitly check for null and skip processing. Ensure
all uses of the function treat null as “invalid key” (not a valid key string) so
no empty-string values are inserted into heldKeys/heldCodes structures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5bcf3970-4c74-46e6-9a16-da87ccdb8da2

📥 Commits

Reviewing files that changed from the base of the PR and between f04822e and b65e589.

📒 Files selected for processing (1)
  • packages/hotkeys/src/constants.ts

Comment on lines +458 to +460
// key can be undefined in rare cases
// (browser extensions synthesizing key events, accessibility tools, certain OS/browser combinations).
if (!key) return ''
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect direct call sites that normalize event.key.
rg -n --type=ts -C3 '\bnormalizeKeyName\s*\(\s*event\.key\s*\)' packages/hotkeys/src

# Inspect likely downstream consumers where '' may be treated as a valid key.
rg -n --type=ts -C3 '\bheldKeysSet\.add\(|\bheldCodesMap\.set\(|\bisModifierKey\s*\(\s*normalizeKeyName\s*\(\s*event\.key\s*\)\s*\)' packages/hotkeys/src

Repository: TanStack/hotkeys

Length of output: 5912


🏁 Script executed:

# First, let's see the full implementation of normalizeKeyName
sed -n '450,470p' packages/hotkeys/src/constants.ts

# Then check the isModifierKey function
rg -n --type=ts 'export function isModifierKey|export const isModifierKey' packages/hotkeys/src -A 10

# Also check what normalizeKeyName does with empty/falsy values
sed -n '440,465p' packages/hotkeys/src/constants.ts

Repository: TanStack/hotkeys

Length of output: 2317


🏁 Script executed:

# Check key-state-tracker to see if there's any guard before adding to Set
sed -n '110,130p' packages/hotkeys/src/key-state-tracker.ts

# Check if normalizeKeyName can actually return empty string, and check parse.ts usage
rg -n --type=ts 'const normalizedKey = normalizeKeyName' packages/hotkeys/src -A 5

Repository: TanStack/hotkeys

Length of output: 1669


🏁 Script executed:

# Check match.ts for how it handles empty eventKey
sed -n '63,75p' packages/hotkeys/src/match.ts

# Check hotkey-manager.ts for releasedKey usage
sed -n '635,650p' packages/hotkeys/src/hotkey-manager.ts

Repository: TanStack/hotkeys

Length of output: 1037


Line 460: Avoid returning '' for invalid keys; use null and require callers to short-circuit.

The guard prevents a crash but lets invalid events (with undefined event.key) flow as empty-string sentinels into downstream stateful paths. For example, in key-state-tracker.ts:113–116, the empty string gets added directly to #heldKeysSet and #heldCodesMap with no validation. Returning null instead makes the invalid state explicit and forces callers to handle it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/hotkeys/src/constants.ts` around lines 458 - 460, The guard in
constants.ts currently returns an empty string for an invalid/undefined key
which lets invalid keys flow downstream; change the sentinel to return null
instead of '' so callers must short-circuit and handle the absence of a key.
Update the function that checks `key` (the if (!key) return ...) to return null,
and then update any callers that consume its result—particularly code paths that
add to `#heldKeysSet` and `#heldCodesMap` in key-state-tracker.ts—to explicitly
check for null and skip processing. Ensure all uses of the function treat null
as “invalid key” (not a valid key string) so no empty-string values are inserted
into heldKeys/heldCodes structures.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used '' so the return type stays string, but happy to be corrected...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

Good question — let me look at the actual downstream call sites to give you a concrete recommendation.


🏁 Script executed:

#!/bin/bash
# Check the full context around key-state-tracker.ts where normalizeKeyName result is used
rg -n --type=ts 'normalizeKeyName' packages/hotkeys/src -A 5 -B 3

# Also check if there's any existing empty-string guard at call sites
rg -n --type=ts 'normalizedKey' packages/hotkeys/src -A 3 -B 3

Length of output: 15632


@ryck Keeping string is the right call here — changing to string | null would cascade through 7+ call sites across the codebase. The '' sentinel is actually fine for most of them:

Call site Effect with ''
match.ts:66 eventKey.length === 1 is false → safely skipped
hotkey-manager.ts:638 '' won't match any registered key → harmless
parse.ts:267 Returns ParsedHotkey with key: '' → no hotkey will match it
sequence-manager.ts:442, recorder-chord.ts:17 isModifierKey('') → false, minor but non-critical

The one concrete problem is key-state-tracker.ts lines 113–116, where no guard exists and '' would be stored directly into #heldKeysSet and #heldCodesMap:

const key = normalizeKeyName(event.key)
if (!this.#heldKeysSet.has(key)) {
  this.#heldKeysSet.add(key)        // ← '' inserted here
  this.#heldCodesMap.set(key, event.code)
}

The fix is simple — a single guard at that call site, keeping your string return type intact:

// key-state-tracker.ts, keydown listener
this.#keydownListener = (event: KeyboardEvent) => {
  const key = normalizeKeyName(event.key)
  if (!key) return  // ← guard against undefined event.key
  if (!this.#heldKeysSet.has(key)) {
    this.#heldKeysSet.add(key)
    this.#heldCodesMap.set(key, event.code)
    this.#syncState()
  }
}

So: your normalizeKeyName guard is correct, and '' is the right sentinel value. The remaining gap is just this one guard in key-state-tracker.ts.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 31, 2026

Open in StackBlitz

@tanstack/angular-hotkeys

npm i https://pkg.pr.new/@tanstack/angular-hotkeys@98

@tanstack/hotkeys

npm i https://pkg.pr.new/@tanstack/hotkeys@98

@tanstack/hotkeys-devtools

npm i https://pkg.pr.new/@tanstack/hotkeys-devtools@98

@tanstack/lit-hotkeys

npm i https://pkg.pr.new/@tanstack/lit-hotkeys@98

@tanstack/preact-hotkeys

npm i https://pkg.pr.new/@tanstack/preact-hotkeys@98

@tanstack/preact-hotkeys-devtools

npm i https://pkg.pr.new/@tanstack/preact-hotkeys-devtools@98

@tanstack/react-hotkeys

npm i https://pkg.pr.new/@tanstack/react-hotkeys@98

@tanstack/react-hotkeys-devtools

npm i https://pkg.pr.new/@tanstack/react-hotkeys-devtools@98

@tanstack/solid-hotkeys

npm i https://pkg.pr.new/@tanstack/solid-hotkeys@98

@tanstack/solid-hotkeys-devtools

npm i https://pkg.pr.new/@tanstack/solid-hotkeys-devtools@98

@tanstack/svelte-hotkeys

npm i https://pkg.pr.new/@tanstack/svelte-hotkeys@98

@tanstack/vue-hotkeys

npm i https://pkg.pr.new/@tanstack/vue-hotkeys@98

@tanstack/vue-hotkeys-devtools

npm i https://pkg.pr.new/@tanstack/vue-hotkeys-devtools@98

commit: 9be240b

@KevinVandy KevinVandy changed the title Add guard to normalizeKeyName() fix: Add guard to normalizeKeyName() Mar 31, 2026
@KevinVandy KevinVandy merged commit 0e46137 into TanStack:main Mar 31, 2026
6 checks passed
@github-actions github-actions bot mentioned this pull request Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

normalizeKeyName doesn't have a guard

2 participants