React wants your components to be predictable. Give it the same state and props, and it should paint the same UI every time. But most real applications cannot survive inside that bubble. They need to reach out. A dashboard needs fresh numbers from a server. A chat widget needs to listen for messages. A timer needs to tick. These operations are side effects, and they sit outside React's render cycle. The useEffect hook is where you put that messy, unpredictable work so your component itself stays honest.
Side Effects: What Belongs Inside useEffect
A side effect is anything that touches the world beyond returning JSX. React's render phase should be pure. When you start fetching data, writing to global variables, or attaching listeners to the DOM, you have stepped out of pure territory.
Common examples include:
- Fetching data from an API
- Setting up timers or intervals
- Adding event listeners to
windowordocument - Updating the browser tab title
- Connecting to WebSockets
These tasks share one trait: they do not belong inside your component's return statement or during the main render logic. Attempting to call a browser API like setInterval directly inside the render body will fire on every render, creating duplicate timers and confusing behavior. useEffect exists precisely to isolate this work and run it at the correct moment.
How the Dependency Array Controls Timing
The second argument to useEffect is the dependency array, and it is the single biggest source of confusion for developers moving from class components. Think of it as a set of variables React watches to decide whether to skip or execute your effect after the current render.
There are three patterns you will use repeatedly.
No dependency array at all. If you omit the array entirely, React assumes you want the effect to run after every single render, including the first one. This is rarely what you need. If your effect performs a network request or a heavy DOM operation, running it on every keystroke or state tweak will tank performance. Use this pattern only when you truly need to re-run something because any prop or state might have changed and you cannot specify which ones.
An empty array []. This tells React to run the effect once, immediately after the component mounts and the DOM is ready. This is the right place for initial data fetches. For example, if your component loads user profile data, you want that request to fire exactly once when the profile page appears, not every time the user interacts with a form lower down the page.
An array with specific variables [count]. This is the precision tool. React compares the current values of these dependencies against their values during the last render. If any of them changed, the effect runs. If nothing in the list changed, React skips the effect entirely.
If you are syncing the browser tab title with a state variable, you would put that variable in the dependency array. React will then update the title only when that value shifts. Leave it out, and the title stays stale. Put unrelated state variables in, and you waste cycles updating the title for changes that do not matter.
Cleanup Is Not Optional
Some effects leave footprints behind. A timer keeps counting. An event listener keeps firing. A WebSocket stays open. When your component unmounts, or even when an effect re-runs because its dependencies changed, React does not automatically clean up the previous effect's residue. That is your job.
You create a cleanup function by returning a function from inside useEffect. React calls this cleanup before it applies the next effect, and once more when the component leaves the screen.
You should use cleanup for:
- Clearing intervals or timeouts with
clearIntervalorclearTimeout - Removing event listeners added to
window,document, or external nodes - Unsubscribing from data streams or services
Neglect this, and you get memory leaks. A component mounts, attaches a scroll listener, unmounts, and the listener remains. The browser holds onto the callback and the DOM nodes it references. Over time, especially in single-page applications with heavy navigation, these ghosts accumulate and slow down the tab. The fix is usually just a few lines: return a function that removes what you added.
Common Mistakes That Ship to Production
Anche gli sviluppatori esperti ricorrono a useEffect quando esiste un'opzione più semplice. Ecco tre pattern che dovrebbero far scattare un segnale di allarme durante la code review.
Loop infiniti. Non aggiornare mai una variabile di stato all'interno di useEffect se quella stessa variabile si trova nel tuo dependency array, a meno che tu non abbia una condizione di controllo che interrompa il ciclo. Se leggi count, lo incrementi e inserisci count come dipendenza, React rileva il cambiamento, esegue il re-render, avvia nuovamente l'effect, lo incrementa ancora e blocca il browser.
Effect non necessari. Non usare useEffect per calcolare un valore partendo da props o state esistenti. Se puoi derivarlo direttamente durante il render, fallo e basta. I valori derivati dovrebbero trovarsi nel corpo del componente o in un calcolo memoizzato con useMemo. Spostarli in un effect divide la logica tra le fasi di render ed effect senza alcun beneficio, rendendo il codice più difficile da seguire.
Lo strumento sbagliato per le azioni dell'utente. `use
