R

React Useeffect Toolkit

Streamline your workflow with this react, useeffect, best, practices. Includes structured workflows, validation checks, and reusable patterns for development.

SkillClipticsdevelopmentv1.0.0MIT
0 views0 copies

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

ScenarioUse Effect?Better Alternative
Sync with DOM/browser APIYes
WebSocket/EventSource subscriptionYes
Third-party library integrationYes
Transform data for renderingNouseMemo or inline
Reset state when props changeNokey prop on component
Respond to user clicksNoEvent handler
Fetch data on mountMaybeReact Query, use(), or loader
Log page viewsYes
Update document.titleYes

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

ParameterTypeDefaultDescription
strictModebooleantrueStrictMode double-invokes effects in dev to find bugs
lintRulestring'exhaustive-deps'ESLint rule for dependency array validation
fetchStrategystring'abort-controller'Cleanup strategy for async effects
subscriptionCleanupstring'return-function'Cleanup pattern: return function or ref-based
debugEffectsbooleanfalseLog effect mount/cleanup/re-run in development

Best Practices

  1. 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.

  2. Use the functional form of setState inside effects — Writing setCount(prev => prev + 1) instead of setCount(count + 1) avoids stale closures and removes count from the dependency array. This prevents unnecessary effect re-runs.

  3. 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.

  4. 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.

  5. 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.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates