Disable/Enable Hotkeys
We are now able to register our hotkeys, scope them to the component (or part of a component) and prevent stale state inside our callback. But our hotkeys are always active. If the component has received focus, it will always trigger. However, sometimes we want to dynamically decide if the hotkey should even be enabled.
There are multiple ways to disable a hotkey. Let's go through all of them.
Dynamically disable or enable
Toggle hotkeys with the enabled
flag
Using the enabled
flag in the options object, we can dynamically tell useHotkeys
if the hotkey should be active or not.
function ExampleComponent() {
const [enabled, setEnabled] = useState(false)
const [count, setCount] = useState(0)
useHotkeys('b', () => setCount(prevCount => prevCount + 1), {
enabled,
})
return (
<div>
<button onClick={() => setEnabled(prevValue => !prevValue)}>Toggle Hotkey</button>
<p>Hotkey is {!enabled && 'not'} enabled.</p>
<p>Pressed the 'b' key {count} times.</p>
</div>
)
}
This is useful to programmatically decide (depending on some internal state of our app or user input) if the hotkey should be active or not.
If we use the returned ref
of the callback to scope our hotkey to a specific sub-tree of our component, note that
this will automatically activate or deactivate our hotkey depending on which part of the page has active focus.
Prevent default browser behavior
Some hotkeys like ctrl + s
are already assigned to a browser function, in this case saving a website to
the users local disk. Sometimes we want to override these hotkeys to implement our own functionality. Maybe we want to
build a text editor or some other user based creation tool that needs a save shortcut to save the current progress. It
would be silly to introduce a new shortcut for saving the users progress, since every user knows that ctrl + s
is the
universal shortcut for saving.
Our callback receives the browsers native event object, so we can just prevent the default behavior there and set up our own custom handler:
function ExampleComponent() {
useHotkeys('cmd+s, ctrl+s', (e) => {
e.preventDefault()
alert('We saved your progress!')
// ... set up our own saving dialog.
})
return (
<div>
<p>Press cmd+s (or ctrl+s for non mac) to open custom save dialog.</p>
</div>
)
}
Please be aware that there are some hotkeys that we cannot override, because they would interfere with a safe browsing
experience for the user. These depend on the browser.
For example in Chrome, most notably those are cmd + w
which closes a tab, cmd + n
for opening a new window and cmd + t
to open a new tab.
Additionally cmd + shift + w
(closing all tabs of the current window), cmd + shift + n
(opening incognito window) and
cmd + shift + t
(reopen the last closed tab) cannot be overridden.
cmd + up + 1..9
on the other hand focuses the corresponding tab of the active window and also cannot be overridden.
Disable callback execution
In addition to completely unbinding the hotkey, we can also disable the execution of the callback with setting the filter
option. The filter
key takes a function that is expected to return a boolean value to indicate if the callback should
get executed or not.
In this scenario the hotkey is still active, but the callback won't be called if the filter function returns false.
function ExampleComponent() {
useHotkeys('option+b', () => alert('Callback got executed'), {
filter: e => {
return true;
}
})
return (
<div>
<p>Pressing 'b' executes the callback, while 'alt+b' won't.</p>
</div>
)
}
With the filter
option in mind we can slightly change our save routine override from above:
function ExampleComponent() {
useHotkeys('cmd+s, ctrl+s', () => alert('We saved your progress!'), {
filter: e => {
e.preventDefault()
return true
}
})
return (
<div>
<p>Press cmd+s (or ctrl+s for non mac) to open custom save dialog.</p>
</div>
)
}
This might be preferred if we want to build a custom hook on top of useHotkeys
that always prevents the default behavior.
Disable default behavior when callback execution is blocked
The above example is sufficient for simple logic to determine whether the callback should be executed.
However, we might come across a scenario where the filter
function returns false
so our callback does not get triggered. Maybe
the logic which determines if the callback should be executed is pretty complex, and we don't want to determine inside the
filter
whether the browser should prevent its default behavior. To simplify this we can use the filterPreventDefault
option:
function ExampleComponent() {
useHotkeys('cmd+s, ctrl+s', () => alert('We saved your progress!'), {
filter: e => {
// .... some logic to deduce if the callback should get triggered or not
// If no logic applies, don't trigger the callback
return false
},
filterPreventDefault: true,
})
return (
<div>
<p>Press cmd+s (or ctrl+s for non mac) to open custom save dialog.</p>
</div>
)
}
This will cause the browser to prevent opening its page saving dialog even though our callback has not been triggered.
Currently the return values of filter
and filterPreventDefault
are counterintuitive. This will change in version 4.
Enable hotkeys on form fields
By default, hotkeys won't trigger when the user is focusing a form field. This is the expected case most of the time.
For example, if we'd listen for the hotkey 'p'
on a review page for apps and a user would try to type This app is simply
the best
into the review input field, the resulting text would be This a is simly
the best
. Same with keystrokes that start with shift + ...
since this normally capitalizes a letter.
So it is good, that useHotkeys
is smart enough to back off during focused form fields. However, sometimes you still
want hotkeys to be enabled even when the user is focusing on an input field. There might be again the need so save
the users progress. Our previous example would not trigger if the user is focusing a form field. Instead, we have to
enable the hotkey on certain form fields. We can use the enableOnTags
option for that which takes an array of form tags:
function ExampleComponent() {
useHotkeys('cmd+s, ctrl+s', (e) => {
e.preventDefault()
alert('We saved your progress!')
// ... set up our own saving dialog.
}, {
enableOnTags: ['INPUT', 'SELECT', 'TEXTAREA']
})
return (
<div>
<p>Press cmd+s (or ctrl+s for non mac) to open custom save dialog.</p>
</div>
)
}
We tell useHotkeys
that the given keystrokes should be listened to and the callback executed even when the user is currently
focusing an input, select or textarea field.
Note that the values in the array are case-sensitive. input
won't work while INPUT
will. This likey to change in
version 4.
Enable hotkeys on tags leveraging contentEditable
The contentEditable
attribute
allows users to edit text even though it is not part of an input field. For example a <div>
tag could have the
contentEditable
attribute. Sometimes we want hotkeys to still trigger, even though a user is focusing an editing a
tags content. A good example for this might be the editor on medium.com. useHotkeys
provides
an option for that:
function ExampleComponent() {
useHotkeys('cmd+b', () => alert('Hotkeys are still working'), {
enableOnContentEditable: true,
})
return (
<div contentEditable={true}>
You can edit this text and still use all the active shortcuts.
</div>
)
}
Before version 3.3.0
, using a tag with contentEditable
set to true would not prevent any hotkeys from triggering.
Be sure to update to at least 3.4.0
to leverage this feature.