useEventListener
Listen to an event on any target, such as window or document, for as long
as the component is mounted.
function App() { const [listening, setListening] = useState(true) const [key, setKey] = useState('-') // A function, so that `window` is read in the browser and not while the // page renders on the server. `null` listens on nothing. useEventListener( () => (listening ? window : null), 'keydown', (event) => setKey(event.key === ' ' ? 'Space' : event.key), ) return ( <div style={{ userSelect: 'none' }}> <div style={{ padding: '2rem', marginBottom: '0.5rem', border: '1px solid currentColor', borderRadius: 8, textAlign: 'center', }} > {listening ? 'Press any key' : 'Not listening'} </div> <div>last key: {key}</div> <label> <input type="checkbox" checked={listening} onChange={(event) => setListening(event.target.checked)} />{' '} Listen on window </label> </div> ) }
Import
import { useEventListener } from '@tremolo-ui/react'
Usage
useEventListener(target, 'keydown', handler, options)
The event name types the handler: 'keydown' gives it a KeyboardEvent.
All of the arguments can be written inline. The handler is read on every event, and the target and the options are compared by what they resolve to, not by identity. The listener is re-attached only when the element, the event name or one of the options actually changes.
The target
The target is an EventTarget, null, or a function returning either.
nulllistens on nothing. Pass it to stop listening for a while- A function is called after every render, in the browser. Use one to
reach an element through a ref, and for
windowanddocumenton a page that is also rendered on the server, where neither exists while rendering
const ref = useRef(null)
useEventListener(() => ref.current, 'pointerenter', handler)
useEventListener(() => window, 'resize', handler)
There is no default target: pass document or window by name.
Options
The fourth argument is the options of addEventListener: a boolean for
capture, or an object with capture, once and passive. signal is left
out, since the hook already decides when the listener goes.
useEventListener(() => window, 'touchmove', handler, { passive: false })
Svelte and Vue
This hook is React only. Both frameworks already have a way to do the same, so
@tremolo-ui/svelte and @tremolo-ui/vue do not repeat it.
- Svelte
- Vue
<svelte:window> and <svelte:document> listen for as long as the component
is mounted:
<svelte:window onkeydown={(event) => (key = event.key)} />
For any other target, or to listen only while something holds, return on()
from svelte/events out of an $effect. It gives back the function that
removes the listener, which is what the effect's cleanup wants:
import { on } from 'svelte/events'
$effect(() => {
if (!listening) return
return on(window, 'keydown', (event) => (key = event.key))
})
VueUse has a useEventListener of
the same shape. It takes the target as a ref or a getter and re-registers when
the target changes. Unlike this hook, it listens on window when the target is
left out.
import { useEventListener } from '@vueuse/core'
useEventListener(window, 'keydown', (event) => (key.value = event.key))
Removing the listener early
The return value removes the listener. It stays removed until the target, the event name or the options change, so a rerender does not bring it back:
const stop = useEventListener(() => window, 'pointermove', (event) => {
if (done(event)) stop()
})
For something to switch on and off, prefer a null target: it says in the
render whether the listener is there.