hermes-agent/optional-skills/web-development/react-best-practices/references/advanced-patterns.md
teknium1 f0b2772d91
feat(skills): add optional react-best-practices skill
Hermes-native port of Vercel's official react-best-practices agent
skill (vercel-labs/agent-skills, MIT). React/Next.js performance
guidance from Vercel Engineering: waterfall elimination, bundle
optimization, server-side patterns, re-render hygiene, and more.
Core SKILL.md plus 8 on-demand reference files; upstream rule IDs
preserved for traceability. Credit: Vercel (vercel-labs).
2026-07-21 03:20:13 -07:00

5.1 KiB

Advanced Patterns (advanced-*)

Impact: LOW. Advanced patterns for specific cases that require careful implementation.

Rules below are from Vercel's react-best-practices skill (MIT, vercel-labs/agent-skills). Rule IDs match upstream filenames for traceability.


advanced-effect-event-deps — Do Not Put Effect Events in Dependency Arrays

Impact: LOW (avoids unnecessary effect re-runs and lint errors)

Effect Event functions do not have a stable identity. Their identity intentionally changes on every render. Do not include the function returned by useEffectEvent in a useEffect dependency array. Keep the actual reactive values as dependencies and call the Effect Event from inside the effect body or subscriptions created by that effect.

Incorrect (Effect Event added as a dependency):

import { useEffect, useEffectEvent } from 'react'

function ChatRoom({ roomId, onConnected }: {
  roomId: string
  onConnected: () => void
}) {
  const handleConnected = useEffectEvent(onConnected)

  useEffect(() => {
    const connection = createConnection(roomId)
    connection.on('connected', handleConnected)
    connection.connect()

    return () => connection.disconnect()
  }, [roomId, handleConnected])
}

Including the Effect Event in dependencies makes the effect re-run every render and triggers the React Hooks lint rule.

Correct (depend on reactive values, not the Effect Event):

import { useEffect, useEffectEvent } from 'react'

function ChatRoom({ roomId, onConnected }: {
  roomId: string
  onConnected: () => void
}) {
  const handleConnected = useEffectEvent(onConnected)

  useEffect(() => {
    const connection = createConnection(roomId)
    connection.on('connected', handleConnected)
    connection.connect()

    return () => connection.disconnect()
  }, [roomId])
}

Reference: React useEffectEvent: Effect Event in deps


advanced-event-handler-refs — Store Event Handlers in Refs

Impact: LOW (stable subscriptions)

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.


advanced-init-once — Initialize App Once, Not Per Mount

Impact: LOW-MEDIUM (avoids duplicate init in development)

Do not put app-wide initialization that must run once per app load inside useEffect([]) of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.

Incorrect (runs twice in dev, re-runs on remount):

function Comp() {
  useEffect(() => {
    loadFromStorage()
    checkAuthToken()
  }, [])

  // ...
}

Correct (once per app load):

let didInit = false

function Comp() {
  useEffect(() => {
    if (didInit) return
    didInit = true
    loadFromStorage()
    checkAuthToken()
  }, [])

  // ...
}

Reference: Initializing the application


advanced-use-latest — useEffectEvent for Stable Callback Refs

Impact: LOW (prevents effect re-runs)

Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures.

Incorrect (effect re-runs on every callback change):

function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
  const [query, setQuery] = useState('')

  useEffect(() => {
    const timeout = setTimeout(() => onSearch(query), 300)
    return () => clearTimeout(timeout)
  }, [query, onSearch])
}

Correct (using React's useEffectEvent):

import { useEffectEvent } from 'react';

function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
  const [query, setQuery] = useState('')
  const onSearchEvent = useEffectEvent(onSearch)

  useEffect(() => {
    const timeout = setTimeout(() => onSearchEvent(query), 300)
    return () => clearTimeout(timeout)
  }, [query])
}