# useEffect & Render Cycle

> When effects run, why the dependency array exists, and how to clean up after yourself.

**Principle:** useEffect is not a lifecycle method — it is a synchronization tool. It answers one question: 'what side effects need to stay in sync with this data?' Every time the dependency array changes, React re-runs the effect to keep things synchronized. When you understand this mental model, dependency arrays stop feeling like magic rules and start making sense.

**Tip:** If you find yourself writing useEffect to fetch data, stop. That is what React Query is for. useEffect is for synchronizing with things outside React — browser APIs, subscriptions, timers.

**Rules:**
- **Always declare dependencies honestly** — Every value from the component scope used inside the effect belongs in the dependency array. If you add eslint-disable to hide a missing dependency, you have a bug.
- **Return a cleanup function when needed** — If your effect creates a subscription, timer, or event listener — clean it up in the return function. Otherwise you get memory leaks and stale handlers.
- **Empty array means once on mount** — [] runs the effect once after the first render. Only use this when the effect truly has no dependencies — not as a shortcut to avoid thinking about deps.
- **Do not use useEffect for data fetching** — Fetching data inside useEffect causes race conditions, no loading state management, and no caching. Use React Query instead.

**Pattern** — `hooks/useWindowSize.ts — effect with cleanup`

```
import { useEffect, useState } from 'react';

interface WindowSize {
  width: number;
  height: number;
}

export function useWindowSize(): WindowSize {
  const [size, setSize] = useState<WindowSize>({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    // The effect: subscribe to resize events
    function handleResize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }

    window.addEventListener('resize', handleResize);

    // The cleanup: unsubscribe when component unmounts
    // or before the effect re-runs
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // No deps — window never changes

  return size;
}
```

**Implementation — Next.js**

In Next.js, window is not available on the server. Use 'use client' and guard browser API access. See all three hooks in the starter template: github.com/sindev08/react-principles-nextjs → src/shared/hooks/

File: `shared/hooks/useDebounce.ts — from react-principles-nextjs starter`

```
'use client';

import { useEffect, useState } from 'react';

// Effect pattern: setTimeout with cleanup
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    // Set a timer to update after the delay
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // Cleanup: clear the timer if value changes before it fires
    // This is what makes it a "debounce" — rapid changes reset the timer
    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]); // ✅ Honest dependencies

  return debouncedValue;
}

// Also in the starter:
// useMediaQuery — useSyncExternalStore with matchMedia (event listener pattern)
// useLocalStorage — cross-tab sync via storage event (cleanup pattern)
```

**Implementation — Vite**

In Vite, all code runs in the browser — no SSR concerns. The pattern is straightforward.

File: `shared/hooks/useWindowSize.ts`

```ts
import { useEffect, useState } from 'react';

export function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    function handleResize() {
      setSize({ width: window.innerWidth, height: window.innerHeight });
    }

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}
```

Read more: https://reactprinciples.dev/cookbook/useeffect-render-cycle

---

Source: https://reactprinciples.dev/nextjs/cookbook/useeffect-render-cycle
All recipes (compact): https://reactprinciples.dev/llms.txt
All recipes (full): https://reactprinciples.dev/llms-full.txt
