React Useeffect Toolkit
Streamline your workflow with this react, useeffect, best, practices. Includes structured workflows, validation checks, and reusable patterns for development.
React useEffect Toolkit
A focused skill for understanding when to use effects, when to avoid them, and how to write correct, leak-free effects in React applications. Covers the most common pitfalls and provides patterns for synchronizing with external systems.
When to Use This Skill
Choose this skill when:
- Debugging infinite loops, stale closures, or missing dependencies in effects
- Synchronizing React state with browser APIs, WebSocket connections, or third-party libraries
- Deciding whether logic belongs in an effect, event handler, or useMemo
- Cleaning up subscriptions, timers, and DOM manipulations properly
- Migrating from class lifecycle methods to hooks
Consider alternatives when:
- Need broad React architecture guidance → use a comprehensive React skill
- Working with data fetching libraries (React Query, SWR) → use those library skills
- Building form validation → use a form patterns skill
- Optimizing render performance → use a performance profiling skill
Quick Start
// The three questions before writing any useEffect: // 1. Is this synchronizing with an external system? → useEffect // 2. Is this computing derived state? → useMemo or inline calc // 3. Is this responding to a user event? → event handler // CORRECT: Synchronizing with an external system (browser API) useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); }, [onClose]); // WRONG: This should be derived state, not an effect // useEffect(() => { setFullName(first + ' ' + last); }, [first, last]); // CORRECT: const fullName = first + ' ' + last;
Core Concepts
Effect Decision Tree
| Scenario | Use Effect? | Better Alternative |
|---|---|---|
| Sync with DOM/browser API | Yes | — |
| WebSocket/EventSource subscription | Yes | — |
| Third-party library integration | Yes | — |
| Transform data for rendering | No | useMemo or inline |
| Reset state when props change | No | key prop on component |
| Respond to user clicks | No | Event handler |
| Fetch data on mount | Maybe | React Query, use(), or loader |
| Log page views | Yes | — |
| Update document.title | Yes | — |
Proper Cleanup Patterns
// Timer cleanup useEffect(() => { const id = setInterval(() => { setCount(c => c + 1); // functional update avoids stale closure }, 1000); return () => clearInterval(id); }, []); // Fetch with abort controller useEffect(() => { const controller = new AbortController(); async function fetchData() { try { const res = await fetch(`/api/items/${id}`, { signal: controller.signal, }); const data = await res.json(); setItems(data); } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return; setError(err as Error); } } fetchData(); return () => controller.abort(); }, [id]); // Resize observer useEffect(() => { if (!ref.current) return; const observer = new ResizeObserver(entries => { for (const entry of entries) { setDimensions({ width: entry.contentRect.width, height: entry.contentRect.height, }); } }); observer.observe(ref.current); return () => observer.disconnect(); }, []);
Using Refs to Avoid Effect Dependencies
// Problem: effect re-runs when onSave changes (inline function) // Bad: useEffect(() => { autoSave(onSave); }, [onSave]); // Solution: ref holds latest callback without triggering effect const onSaveRef = useRef(onSave); useEffect(() => { onSaveRef.current = onSave; }); useEffect(() => { const id = setInterval(() => { onSaveRef.current(); // always calls latest version }, 30000); return () => clearInterval(id); }, []); // stable dependency array
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
strictMode | boolean | true | StrictMode double-invokes effects in dev to find bugs |
lintRule | string | 'exhaustive-deps' | ESLint rule for dependency array validation |
fetchStrategy | string | 'abort-controller' | Cleanup strategy for async effects |
subscriptionCleanup | string | 'return-function' | Cleanup pattern: return function or ref-based |
debugEffects | boolean | false | Log effect mount/cleanup/re-run in development |
Best Practices
-
Always include a cleanup function for subscriptions and listeners — Effects that add event listeners, start timers, or open connections must return a cleanup function. Without cleanup, listeners accumulate on every re-render and timers fire after unmount.
-
Use the functional form of setState inside effects — Writing
setCount(prev => prev + 1)instead ofsetCount(count + 1)avoids stale closures and removescountfrom the dependency array. This prevents unnecessary effect re-runs. -
Never suppress exhaustive-deps warnings — ESLint's exhaustive-deps rule catches real bugs. If adding a dependency causes infinite loops, the solution is to stabilize the dependency (with
useCallback,useMemo, or refs), not to disable the rule. -
Keep effects focused on a single concern — An effect that sets up a WebSocket AND logs analytics AND syncs with localStorage should be three separate effects. Each effect gets its own cleanup logic and dependency array.
-
Move data transformations out of effects — If you're setting state based on other state inside an effect, the logic probably belongs in
useMemo, a derived variable, or the event handler that triggered the change. Effects are for external system synchronization.
Common Issues
Effect runs twice in development — React StrictMode intentionally double-invokes effects to surface missing cleanup. This is expected behavior and does not happen in production. If double-invocation causes visible bugs, the cleanup function is incomplete or missing.
Stale state values inside effect closures — Effects capture state values at the time they're created. Long-running effects (timers, WebSocket handlers) see stale data. Use refs for values that need to stay current, or use functional state updates.
Object/array dependencies cause infinite re-runs — Objects and arrays are compared by reference, not by value. { a: 1 } !== { a: 1 } on every render. Destructure to primitive values in the dependency array, or memoize the object with useMemo.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.