React 18 ha introdotto useSyncExternalStore, un hook che permette ai componenti di leggere dati da qualsiasi fonte esterna all'albero di React — API del browser, WebSocket o store di terze parti — senza i glitch visivi che affliggevano il vecchio pattern di duplicazione di tali dati tramite useState e useEffect. Se hai mai visto un flicker nell'interfaccia utente perché una parte mostrava le dimensioni della finestra non aggiornate mentre un'altra rifletteva già quelle nuove, questo hook è la soluzione che stavi aspettando.
Perché lo stato esterno necessita di un nuovo hook
I componenti React sono sempre stati in grado di iscriversi a elementi che esistono al di fuori del proprio ambito: l'oggetto window, uno store Redux, una connessione WebSocket. L'approccio convenzionale consisteva nel configurare un'iscrizione all'interno di un useEffect, spingere i valori in arrivo nello stato locale tramite setState e lasciare che React eseguisse il re-render. Questo funziona bene quando React renderizza in modo sincrono, ma React 18 ha introdotto il rendering concorrente (concurrent rendering), in cui un componente può essere renderizzato più volte prima che il browser effettui effettivamente il paint. In questa modalità, il pattern "mirror-into-local-state" può causare il tearing — ovvero diverse parti dell'interfaccia utente che leggono snapshot differenti dello stesso valore esterno durante un singolo ciclo di rendering.
useSyncExternalStore è stato creato per prevenire il tearing. Richiede alla fonte esterna uno snapshot (il valore corrente) e una funzione subscribe (per essere notificati dei cambiamenti). React chiama subscribe quando il componente viene montato e si disiscrive automaticamente allo smontaggio (unmount). Ogni volta che lo snapshot restituito da getSnapshot cambia, React pianifica un render che legge lo stesso snapshot per l'intero albero dei componenti, garantendo la coerenza.
Le due funzioni richieste
| Funzione | Cosa fa |
|---|---|
| subscribe | Riceve una callback che deve essere chiamata ogni volta che il valore esterno cambia. Restituisce una funzione di unsubscribe che React chiamerà durante la pulizia (cleanup). |
| getSnapshot | Restituisce il valore corrente dalla fonte esterna. Deve essere referentially stable — lo stesso oggetto funzione a ogni render — affinché React possa confrontare correttamente gli snapshot. |
Un'implementazione minima per lo stato online del browser è la seguente:
function useOnlineStatus() {
return useSyncExternalStore(
(onChange) => {
window.addEventListener('online', onChange);
window.addEventListener('offline', onChange);
return () => {
window.removeEventListener('online', onChange);
window.removeEventListener('offline', onChange);
};
},
() => navigator.onLine
);
}
Quando un componente chiama useOnlineStatus(), React lo renderizzerà nuovamente solo quando navigator.onLine cambia effettivamente stato, e ogni render vedrà lo stesso valore.
Regole per evitare che l'hook causi problemi
Restituisci la porzione minima di dati di cui un componente ha bisogno.
Se lo store esterno contiene un oggetto grande ma a un componente interessa solo un campo, restituisci solo quel campo. Restituzioni più ampie causano render non necessari.Memorizza
getSnapshot.
Se ricrei la funzione a ogni render, React la tratterà come una nuova fonte e potrebbe generare un loop infinito. Avvolgila inuseCallbacko definiscila all'esterno del componente.Evita di restituire nuovi oggetti a ogni chiamata.
Restituire un nuovo oggetto (ad es.{ count: store.getCount() }) crea un nuovo riferimento a ogni render, portando React a pensare che lo snapshot sia cambiato e innescando un loop di rendering infinito. Usa tipi primitivi, stringhe, numeri o oggetti memorizzati.Non usarlo per lo stato interno del componente.
useStaterimane lo strumento corretto per i dati che vivono solo all'interno del componente.useSyncExternalStoreaggiunge un overhead non necessario per lo stato locale.
Quando utilizzarlo
- API del browser – dimensione della finestra, media query,
navigator.onLine, stato della batteria. - Store framework-agnostic – Zustand, Redux, MobX o qualsiasi store personalizzato che esponga un'API di iscrizione.
- Fonti mutabili che si aggiornano al di fuori di React – messaggi WebSocket, eventi di modifica di IndexedDB, notifiche dei Service Worker.
Se la tua fonte dati vive già all'interno di React (ad es. lo stato di un componente genitore), continua a usare useState o il context.
Cosa dice la community
Gli utenti che lo hanno adottato precocemente riferiscono che useSyncExternalStore elimina il flicker osservato durante il ridimensionamento delle finestre in una configurazione con rendering concorrente. Alcune librerie hanno già migrato i propri hook interni a questa API, promettendo un comportamento più prevedibile tra le diverse versioni di React. Il compromesso è un carico mentale leggermente superiore: gli sviluppatori devono prestare attenzione alla stabilità delle due funzioni ed evitare di restituire nuovi oggetti a ogni chiamata.
Cosa monitorare successivamente
I prossimi rilasci di React potrebbero rendere più rigoroso il contratto attorno a useSyncExternalStore, aggiungendo potenzialmente degli helper integrati per le API del browser più comuni. Tieni d'occhio il blog ufficiale di React per eventuali deprecazioni del vecchio pattern che ricalca useEffect. Nel frattempo, l'hook è stabile e fa parte dell'API pubblica, quindi puoi rifattorizzare in sicurezza il codice esistente relativo allo stato esterno.
Una rapida prova pratica
Una demo live che mostra la sincronizzazione della dimensione della finestra, il rilevamento dello stato online/offline e un semplice store Zustand è disponibile all'indirizzo https://usesyncexternalstore.vercel.app/. Il codice sorgente, completo di commenti, è disponibile su https://github.com/dev48v/usesyncexternalstore.
In sintesi: useSyncExternalStore fornisce a React un ponte affidabile verso qualsiasi dato mutabile che risiede al di fuori del suo ciclo di rendering, prevenendo il tearing e i render non necessari. Applicalo a fonti esterne, mantieni lo snapshot minimo e stabile, e lascia che React si occupi del lavoro pesante per garantire la coerenza.
