skills/react-effect-and-handler-patterns/SKILL.md
React effect and event handler optimization — narrow deps, handler refs, useEffectEvent, global listener dedup, one-time init. Use when writing or fixing useEffect, event handlers, or component initialization.
npx skillsauth add ihj04982/my-cursor-settings react-effect-and-handler-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Patterns for efficient useEffect usage, stable event handler references, and proper initialization.
Specify primitive dependencies instead of objects to minimize effect re-runs.
Bad (re-runs on any user field change):
useEffect(() => {
console.log(user.id);
}, [user]);
Good (re-runs only when id changes):
useEffect(() => {
console.log(user.id);
}, [user.id]);
Derive booleans from continuous values:
// Bad: runs on width=767, 766, 765...
useEffect(() => {
if (width < 768) enableMobileMode();
}, [width]);
// Good: runs only on boolean transition
const isMobile = width < 768;
useEffect(() => {
if (isMobile) enableMobileMode();
}, [isMobile]);
If a side effect is triggered by a specific user action, run it in the handler — not as state + effect.
Bad (event modeled as state + effect):
const [submitted, setSubmitted] = useState(false);
const theme = useContext(ThemeContext);
useEffect(() => {
if (submitted) {
post('/api/register');
showToast('Registered', theme);
}
}, [submitted, theme]);
Good:
const theme = useContext(ThemeContext);
function handleSubmit() {
post('/api/register');
showToast('Registered', theme);
}
Reference: Should this code move to an event handler?
Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
Bad (re-subscribes on every render):
function useWindowEvent(event: string, handler: (e) => void) {
useEffect(() => {
window.addEventListener(event, handler);
return () => window.removeEventListener(event, handler);
}, [event, handler]);
}
Good (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]);
}
useEffectEvent provides a cleaner API: a stable function that always calls the latest handler without being in effect deps.
Bad (effect re-runs on every callback change):
useEffect(() => {
const timeout = setTimeout(() => onSearch(query), 300);
return () => clearTimeout(timeout);
}, [query, onSearch]);
Good:
import { useEffectEvent } from 'react';
const onSearchEvent = useEffectEvent(onSearch);
useEffect(() => {
const timeout = setTimeout(() => onSearchEvent(query), 300);
return () => clearTimeout(timeout);
}, [query]);
Register global event listeners once and share across component instances.
Bad (N instances = N listeners):
function useKeyboardShortcut(key: string, callback: () => void) {
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.metaKey && e.key === key) callback();
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [key, callback]);
}
Good (N instances = 1 listener via module-level Map + useSWRSubscription):
const keyCallbacks = new Map<string, Set<() => void>>();
function useKeyboardShortcut(key: string, callback: () => void) {
useEffect(() => {
if (!keyCallbacks.has(key)) keyCallbacks.set(key, new Set());
keyCallbacks.get(key)!.add(callback);
return () => {
const set = keyCallbacks.get(key);
if (set) { set.delete(callback); if (set.size === 0) keyCallbacks.delete(key); }
};
}, [key, callback]);
useSWRSubscription('global-keydown', () => {
const handler = (e: KeyboardEvent) => {
if (e.metaKey && keyCallbacks.has(e.key))
keyCallbacks.get(e.key)!.forEach((cb) => cb());
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
});
}
App-wide init (analytics, auth check) must run once at module load, not inside useEffect([]) which re-runs on remount.
Bad (runs twice in dev, re-runs on remount):
function Comp() {
useEffect(() => {
loadFromStorage();
checkAuthToken();
}, []);
}
Good:
let didInit = false;
function Comp() {
useEffect(() => {
if (didInit) return;
didInit = true;
loadFromStorage();
checkAuthToken();
}, []);
}
Reference: Initializing the application
useEffectEventdevelopment
Conduct WCAG 2.2 accessibility audits with automated testing, manual verification, and remediation guidance. Use when auditing websites for accessibility, fixing WCAG violations, or implementing accessible design patterns.
research
Generate high-entropy research (자료조사) and ideas (아이디어) using Verbalized Sampling to avoid mode collapse and maximize creativity and novelty.
development
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
documentation
Sync documentation from source-of-truth (package.json, .env.example). Generates CONTRIB.md, RUNBOOK.md. Use when updating project docs or after adding scripts/env vars.