# useEffect, Dependencies and Cleanup — React

Source: https://www.skillbyai.com/en/react/e-lifecycle

> Setup, cleanup, re-run when dependencies change.

## Sync with external systems

`useEffect(setup, deps)` runs `setup` after the component renders, to synchronise with something outside React: a subscription, a timer, a browser API, a non-React widget. If setup returns a function, React calls it as **cleanup** before re-running the effect and when the component unmounts. The **dependency array** lists values the effect uses; React re-runs the effect only when one of them changes. In development, Strict Mode runs setup and cleanup an extra time to reveal missing cleanup.

## Synchronise with the outside world

Effects run after rendering to sync with systems outside React, with cleanup.

![Three ideas: lifecycle and cleanup, data fetching, when not to use effects.](assets/figures/react/section-4-map.svg) — Figure 4.1 — Lifecycle, fetching and avoiding effects.

## Effect lifecycle with changing dependencies, run

I ran this with React 19.3 and react-dom 19.3 on Node.js 22, compiling JSX with esbuild 0.28. Browser-style examples use jsdom 29 as a fake DOM (imported from a small setup-dom.mjs file) and act() to apply updates. Rendering with zone IST subscribes once; re-rendering with the same zone does nothing; switching to UTC cleans up IST then subscribes UTC; unmounting cleans up UTC.

```jsx
import "./setup-dom.mjs";   // test setup: jsdom provides a browser-like DOM in Node
import { useState, useEffect, act } from "react";
import { createRoot } from "react-dom/client";

const log = [];
function Clock({ zone }) {
  useEffect(() => {
    log.push(`subscribe ${zone}`);
    return () => log.push(`unsubscribe ${zone}`);   // cleanup
  }, [zone]);
  return <p>{zone}</p>;
}
function App({ zone, show }) { return show ? <Clock zone={zone} /> : <p>hidden</p>; }
const root = createRoot(document.getElementById("root"));
await act(() => root.render(<App zone="IST" show />));
await act(() => root.render(<App zone="IST" show />));   // same dependency: effect does not re-run
await act(() => root.render(<App zone="UTC" show />));   // dependency changed
await act(() => root.render(<App zone="UTC" show={false} />));   // unmount
console.log(log.join("\n"));
```

Output:

```
subscribe IST
unsubscribe IST
subscribe UTC
unsubscribe UTC
```

## Every subscription needs cleanup

If setup adds a listener, timer or connection, the cleanup must remove it, or you will leak and duplicate them.

**Quiz:** When does React run an effect's cleanup function?

- [ ] Before the first render
- [ ] Only on page reload
- [ ] Never
- [x] Before re-running the effect and when the component unmounts

*Answer:* Before re-running the effect and when the component unmounts. Cleanup undoes setup.
