use è un'API di React 19 che legge un valore da una promise o da un context mentre un componente si renderizza. use(promise) mette in pausa il componente finché la promise non si risolve, mostrando nel frattempo il fallback del <Suspense> più vicino; use(context) funziona come useContext ma può stare dentro un if o un ciclo.
La funzione fetchUser qui sotto fa le veci di una richiesta reale.
Il primo rendering mostra "Loading..." per un secondo, poi il profilo. Clicca User 2: il fallback ritorna mentre la nuova promise è in attesa. Profile non ha un proprio stato di caricamento; è scritto come se i dati fossero già lì.
Come funziona use(promise)
Quando Profile chiama use(userPromise):
- Se la promise si è risolta,
userestituisce il suo valore e il rendering continua. - Se è ancora in attesa, React smette di renderizzare
Profilee mostra il fallback del<Suspense>più vicino sopra di esso. Quando la promise si conclude, React renderizza di nuovoProfile, e questa voltauserestituisce il valore. - Se è stata rifiutata, React mostra invece l'error boundary più vicino.
React ricorda il risultato sull'oggetto promise stesso. Per questo la promise deve essere lo stesso oggetto al rendering successivo.
Per tenere sullo schermo il vecchio profilo mentre si carica il successivo, imposta la promise dentro una transizione. React allora aspetta invece di mostrare di nuovo il fallback:
<button onClick={() => startTransition(() => setUserPromise(fetchUser(2)))}>User 2</button>
Dove creare la promise
Questa è la parte che fa inciampare. Scrivere la chiamata di fetch dentro il componente che la legge sembra naturale e non funziona:
// Do not do this
function Profile({ id }) {
const user = use(fetchUser(id)); // a new promise on every render
return <p>{user.name}</p>;
}
Ogni rendering chiama fetchUser e ottiene una nuova promise in attesa. Profile si sospende. Quando quella promise si risolve, React renderizza di nuovo Profile, che crea un'altra promise in attesa, che si sospende di nuovo. Il componente non mostra mai i dati, e la scheda di rete si riempie di richieste.
Crea la promise in un punto che non venga rieseguito a ogni rendering del lettore:
- In un genitore, tenuta nello stato (come nel primo esempio) o creata in un gestore di eventi.
- A livello di modulo, quando i dati non dipendono dalle props:
const configPromise = fetchConfig();sopra il componente. - In una cache indicizzata per argomenti, così lo stesso id restituisce la stessa promise.
- In un framework: un Server Component può passare una promise a un componente client come prop, e i loader delle route fanno lo stesso. È il caso per cui
useè stato progettato.
Ecco la versione con la cache, così un componente può chiedere dati per id e ottenere comunque una promise stabile:
Clicca sulle tre città, poi torna a una che hai già aperto. La console registra una richiesta per città, e una città che hai già visto compare senza il fallback, perché la sua promise si è già risolta. Elimina il controllo if (!cache.has(city)) e il componente torna a sospendersi per sempre.
Una cache reale ha anche bisogno di un modo per far scadere le voci. Librerie di dati e framework se ne occupano, ed è per questo che la maggior parte delle app ottiene le proprie promise da uno di loro.
use(context), anche dentro un if
use(SomeContext) restituisce lo stesso valore di useContext(SomeContext). La differenza è dove puoi chiamarlo. Ogni altro hook deve essere eseguito al livello più alto, nello stesso ordine a ogni rendering (le regole degli hook). use può essere chiamato dopo un return anticipato, dentro una condizione o in un ciclo.
Clicca il pulsante per passare da un ramo all'altro. Sostituisci use(UserContext) con useContext(UserContext) e il codice viola le regole degli hook, perché l'hook verrebbe eseguito in alcuni rendering e in altri no.
use ha comunque dei limiti: chiamalo solo mentre renderizzi un componente o dentro un hook personalizzato. In un gestore di eventi o in un effetto, leggi il context da un valore che hai già ottenuto durante il rendering.
Errori: error boundary, non try/catch
Quando la promise viene rifiutata, l'errore va all'error boundary più vicino, allo stesso modo di un errore di rendering.
Apri Post 2 e il confine mostra il messaggio di errore. Apri di nuovo Post 1 e funziona: key={id} dà al confine uno stato nuovo per ogni post, così non continua a mostrare il vecchio errore.
Non puoi intercettare un use in attesa o rifiutato con try/catch. Mentre la promise è in attesa, use interrompe il rendering generando un valore speciale, e un catch attorno lo inghiottirebbe. Il valore intercettato è un errore il cui messaggio inizia con "Suspense Exception: This is not a real error!", e in sviluppo React registra anche "use was called from inside a try/catch block". Per mostrare un valore di ripiego invece di una schermata di errore, gestisci il rifiuto sulla promise prima di passarla verso il basso:
const postPromise = fetchPost(id).catch(() => 'This post could not be loaded.');
use a confronto con il recupero dei dati in useEffect
Lo schema classico lato client recupera i dati in un effetto e tiene tre pezzi di stato:
function Profile({ id }) {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
fetchUser(id).then(
(u) => !ignore && setUser(u),
(e) => !ignore && setError(e),
);
return () => {
ignore = true;
};
}, [id]);
if (error) return <p>Error</p>;
if (!user) return <p>Loading...</p>;
return <p>{user.name}</p>;
}
useEffect | use | |
|---|---|---|
| Quando parte la richiesta | dopo che il primo rendering è sullo schermo | quando la promise viene creata, anche prima che il componente si renderizzi |
| Stato di caricamento | il tuo if (!user) | il <Suspense> più vicino |
| Errori | il tuo stato error | l'error boundary più vicino |
| Race condition | le gestisci tu (ignore) | il componente legge sempre la promise che ha ricevuto |
| Chi crea la promise | il componente | un genitore, una cache o un framework |
La versione con effetto è autonoma, ed è per questo che resta l'approccio più comune nelle app senza framework; la pagina sul recupero dei dati la tratta per intero. use dà il meglio quando qualcosa sopra il componente avvia la richiesta in anticipo e passa la promise verso il basso.
Domande frequenti
Cosa fa l'hook use in React?
use(resource) restituisce il valore di una promise o di un context. Con una promise, il componente si sospende finché non si risolve e il <Suspense> più vicino mostra il suo fallback; se viene rifiutata, compare invece l'error boundary più vicino.
use può essere chiamato in modo condizionale?
Sì. use è l'unico hook che puoi chiamare dentro istruzioni if, cicli e dopo un return anticipato. Deve comunque essere chiamato da un componente o da un hook, mai da un gestore di eventi o da un effetto.
Perché use(fetch()) dentro il mio componente va in loop per sempre?
Ogni rendering crea una nuova promise. Il componente si sospende su di essa, React renderizza di nuovo quando si risolve, e quel rendering crea un'altra promise in attesa. Crea la promise fuori dal rendering: nello stato di un genitore, a livello di modulo, in una cache o in un loader di un framework.
Posso racchiudere use in try/catch?
No. Quando una promise è in attesa, use interrompe il rendering generando un'eccezione, e intercettarla lo rompe. Gestisci una promise rifiutata con un error boundary, oppure aggiungi .catch() alla promise così si risolve in un valore di ripiego.
Devo sostituire il recupero dei dati con useEffect con use?
use ha bisogno di una promise creata una volta e riutilizzata, che è ciò che ti danno framework e librerie di dati. In una semplice app lato client, recuperare dati in un effetto (o con una libreria come TanStack Query) è ancora comune e va bene.