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).
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])
}