Skip to main content

Advanced Event Handler Refs

Source: .agents/references/coding-standard/vercel-react-best-practices/rules/advanced-event-handler-refs.md

Metadata

  • title: Store Event Handlers in Refs
  • impact: LOW
  • impactDescription: stable subscriptions
  • tags: advanced, hooks, refs, event-handlers, optimization

Content

Store Event Handlers in Refs

Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.

Incorrect (re-subscribes on every render):

function useWindowEvent(event: string, handler: (e) => void) {
useEffect(() => {
window.addEventListener(event, handler)
return () => window.removeEventListener(event, handler)
}, [event, handler])
}

Correct (stable subscription):

function useWindowEvent(event: string, handler: (e) => void) {
const handlerRef = useRef(handler)
useEffect(() => {
handlerRef.current = handler
}, [handler])

useEffect(() => {
const listener = (e) => handlerRef.current(e)
window.addEventListener(event, listener)
return () => window.removeEventListener(event, listener)
}, [event])
}

Alternative: use useEffectEvent if you're on latest React:

import { useEffectEvent } from 'react'

function useWindowEvent(event: string, handler: (e) => void) {
const onEvent = useEffectEvent(handler)

useEffect(() => {
window.addEventListener(event, onEvent)
return () => window.removeEventListener(event, onEvent)
}, [event])
}

useEffectEvent provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.