# useEffect vs useLayoutEffect: quando cambia il risultato

useEffect gira dopo il paint del browser, useLayoutEffect gira prima e in modo sincrono: il secondo serve quando devi misurare il DOM prima che l’utente veda il frame.

> Pagina: https://ui.eneawork.it/blog/useeffect-vs-uselayouteffect
> Markdown: https://ui.eneawork.it/blog/useeffect-vs-uselayouteffect.md

*2026-11-01*

La differenza è il momento: useEffect gira dopo che il browser ha dipinto, useLayoutEffect gira prima del paint e in modo sincrono. Se il risultato dipende da una misura del DOM vince il secondo, altrimenti il primo costa meno.

Il catalogo EneUI conta 111.985 componenti, 1.556 blocchi e 12.808 icone da 1.472 librerie, misurati da /api/stats il 1 novembre 2026. La cifra viene da una query all’endpoint, non da una stima: conta i record pubblicati e non dice quanti usino questa API correttamente.

## L’ordine dentro un frame

React aggiorna il DOM, poi esegue i layout effect, poi lascia dipingere il browser, poi esegue gli effect passivi. Il layout effect è sincrono: il paint aspetta che la funzione sia tornata.

## Il caso in cui il risultato cambia

Un tooltip che deve centrarsi sull’ancora non conosce la propria larghezza finché non è nel DOM. Con useEffect il primo frame lo mostra spostato e il secondo lo corregge: l’occhio vede il salto. Con useLayoutEffect misura e correzione avvengono prima del paint.

function Tooltip({ anchor, children }) { const ref = useRef(null); const [left, setLeft] = useState(0); useLayoutEffect(() => { const width = ref.current.getBoundingClientRect().width; setLeft(anchor.x - width / 2); }, [anchor.x]); return ( {children} ); }

## Quando useEffect è la scelta giusta

Dati, sottoscrizioni, log e titolo della pagina non hanno bisogno di fermare il paint: un effect passivo lascia disegnare il browser e parte subito dopo. La regola pratica è partire da useEffect e passare all’altro solo quando una misura visibile lo richiede.

## Il costo

Ogni layout effect allunga il frame: se legge e scrive il DOM in un ciclo, il browser aspetta. Un setState dentro useLayoutEffect provoca un render sincrono extra, e una lista lunga di questi componenti si sente nello scroll.

## Provarlo in locale

npm create vite@latest effect-demo -- --template react-ts cd effect-demo npm install npm run dev

Il template react-ts di Vite, con licenza MIT, include React 19 e TypeScript: apri src/App.tsx, sostituisci il contenuto con il componente qui sopra e osserva la pagina mentre la finestra cambia larghezza. Se lo stesso componente finisce in un render sul server, React stampa un avviso: sul server non esiste DOM da misurare.

## Record vivi nel catalogo

Due record mostrano il problema in produzione: dropdown-menu, che sulle primitive Radix calcola la posizione prima del paint, e theme-toggle, che cambia icona senza mostrare il frame intermedio. Il resto è tra i 111.985 componenti e i 1.556 blocchi.

## La limitazione

Su un componente renderizzato lato server useLayoutEffect non può misurare niente: l’HTML arriva senza la correzione e il valore giusto compare solo dopo l’idratazione. Per quel caso la misura va spostata in un effetto passivo o risolta in CSS.

## Dove continuare

L’ordine delle chiamate è in regole degli hook; cosa cambia nella versione 19 è in React 19; il confronto fra framework sul costo del primo render è in framework frontend a confronto.

## FAQ

### Perché useEffect non basta per misurare il DOM?

Perché parte dopo il paint: la misura arriva quando l’utente ha già visto il frame sbagliato. useLayoutEffect gira prima e permette di correggere lo stile senza mostrare il passaggio.

### useLayoutEffect gira anche sul server?

No. Sul server non c’è layout da misurare, quindi React non lo esegue e riserva l’effetto all’idratazione, con un avviso in console durante il render sul server.

### Quale usare per leggere localStorage?

useEffect, perché il valore non dipende dal layout. Il primo frame mostra il valore predefinito e il secondo quello salvato: se quel salto si vede, il rimedio è leggere il valore in un effetto di inizializzazione lato client.

### useLayoutEffect può causare un loop infinito?

Sì, se dentro l’effetto aggiorni uno stato che compare nelle dipendenze. Il ciclo è sincrono, quindi blocca la pagina invece di rallentarla: React avvisa con “Maximum update depth exceeded”.

### Come vedo la differenza in DevTools?

Nel pannello Performance, il tempo fra la scrittura del DOM e il primo paint include i layout effect. Confronta due registrazioni con lo stesso componente e i due hook per vedere quanto pesa la versione sincrona.
