Menu

Le hook use de React : lire promesses et contexte au rendu

use lit la valeur d'une promesse ou d'un contexte pendant le rendu. Avec une promesse, il se suspend jusqu'à l'arrivée des données et affiche le contenu de repli du Suspense le plus proche. Contrairement aux autres hooks, il peut s'exécuter dans des instructions if et des boucles.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

use est une API de React 19 qui lit une valeur depuis une promesse ou un contexte pendant le rendu d'un composant. use(promise) met le composant en pause jusqu'à la résolution de la promesse, en affichant entre-temps le contenu de repli du <Suspense> le plus proche ; use(context) fonctionne comme useContext mais peut se trouver dans un if ou une boucle.

La fonction fetchUser ci-dessous remplace une vraie requête.

Le premier rendu affiche "Loading..." pendant une seconde, puis le profil. Cliquez sur User 2 : le contenu de repli revient pendant que la nouvelle promesse est en attente. Profile n'a pas d'état de chargement propre ; il est écrit comme si les données étaient déjà là.

Comment fonctionne use(promise)

Quand Profile appelle use(userPromise) :

  • Si la promesse est résolue, use renvoie sa valeur et le rendu continue.
  • Si elle est encore en attente, React arrête le rendu de Profile et affiche le contenu de repli du <Suspense> le plus proche au-dessus. Quand la promesse se règle, React refait le rendu de Profile, et cette fois use renvoie la valeur.
  • Si elle a été rejetée, React affiche à la place l'error boundary la plus proche.

React mémorise le résultat sur l'objet promesse lui-même. C'est pourquoi la promesse doit être le même objet au rendu suivant.

Pour garder l'ancien profil à l'écran pendant le chargement du suivant, définissez la promesse dans une transition. React attend alors au lieu d'afficher à nouveau le contenu de repli :

<button onClick={() => startTransition(() => setUserPromise(fetchUser(2)))}>User 2</button>

Où créer la promesse

C'est le point qui piège le plus. Écrire l'appel de fetch dans le composant qui le lit semble naturel et ne fonctionne pas :

// Do not do this
function Profile({ id }) {
    const user = use(fetchUser(id)); // a new promise on every render
    return <p>{user.name}</p>;
}

Chaque rendu appelle fetchUser et obtient une nouvelle promesse en attente. Profile se suspend. Quand cette promesse se résout, React refait le rendu de Profile, qui crée une autre promesse en attente, qui se suspend à nouveau. Le composant n'affiche jamais les données, et l'onglet réseau se remplit de requêtes.

Créez la promesse à un endroit qui ne se réexécute pas à chaque rendu du lecteur :

  • Dans un parent, gardée dans l'état (comme dans le premier exemple) ou créée dans un gestionnaire d'événement.
  • Au niveau du module, quand les données ne dépendent pas des props : const configPromise = fetchConfig(); au-dessus du composant.
  • Dans un cache indexé par les arguments, pour que le même identifiant renvoie la même promesse.
  • Dans un framework : un Server Component peut passer une promesse à un composant client en prop, et les loaders de routes font de même. C'est le cas pour lequel use a été conçu.

Voici la version avec cache, pour qu'un composant puisse demander des données par identifiant tout en obtenant une promesse stable :

Parcourez les trois villes, puis revenez à une ville déjà ouverte. La console affiche une requête par ville, et une ville déjà vue apparaît sans contenu de repli, car sa promesse est déjà résolue. Supprimez la vérification if (!cache.has(city)) et le composant recommence à se suspendre à l'infini.

Un vrai cache a aussi besoin d'un moyen de faire expirer les entrées. Les bibliothèques de données et les frameworks s'en chargent, c'est pourquoi la plupart des applications obtiennent leurs promesses de l'un d'eux.

use(context), même dans un if

use(SomeContext) renvoie la même valeur que useContext(SomeContext). La différence tient à l'endroit où vous pouvez l'appeler. Tous les autres hooks doivent s'exécuter au niveau supérieur, dans le même ordre à chaque rendu (les règles des hooks). use peut être appelé après un retour anticipé, dans une condition ou dans une boucle.

Cliquez sur le bouton pour passer d'une branche à l'autre. Remplacez use(UserContext) par useContext(UserContext) et le code enfreint les règles des hooks, puisque le hook s'exécuterait à certains rendus et pas à d'autres.

use a quand même des limites : appelez-le uniquement pendant le rendu d'un composant ou dans un hook personnalisé. Dans un gestionnaire d'événement ou un effet, lisez le contexte via une valeur déjà obtenue pendant le rendu.

Erreurs : error boundaries, pas try/catch

Quand la promesse est rejetée, l'erreur va à l'error boundary la plus proche, comme une erreur de rendu.

Ouvrez Post 2 et la boundary affiche le message d'erreur. Rouvrez Post 1 et tout fonctionne : key={id} donne à la boundary un état neuf pour chaque article, pour qu'elle ne continue pas d'afficher l'ancienne erreur.

Vous ne pouvez pas intercepter un use en attente ou rejeté avec try/catch. Pendant que la promesse est en attente, use interrompt le rendu en levant une valeur spéciale, et un catch autour l'avalerait. La valeur interceptée est une erreur dont le message commence par "Suspense Exception: This is not a real error!", et en développement React affiche aussi "use was called from inside a try/catch block". Pour afficher une valeur de repli plutôt qu'un écran d'erreur, gérez le rejet sur la promesse avant de la transmettre :

const postPromise = fetchPost(id).catch(() => 'This post could not be loaded.');

use comparé à la récupération dans useEffect

Le pattern client classique récupère les données dans un effet et garde trois éléments d'état :

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>;
}
useEffectuse
Quand la requête démarreaprès l'affichage du premier renduà la création de la promesse, éventuellement avant le rendu du composant
État de chargementvotre propre if (!user)le <Suspense> le plus proche
Erreursvotre propre état errorl'error boundary la plus proche
Conditions de coursec'est vous qui vous en protégez (ignore)le composant lit toujours la promesse qu'on lui a donnée
Qui crée la promessele composantun parent, un cache ou un framework

La version avec effet est autonome, c'est pourquoi elle reste l'approche la plus courante dans les applications sans framework ; la page sur la récupération de données la traite en détail. use brille quand quelque chose au-dessus du composant lance la requête tôt et transmet la promesse vers le bas.

Questions fréquentes

Que fait le hook use dans React ?

use(resource) renvoie la valeur d'une promesse ou d'un contexte. Avec une promesse, le composant se suspend jusqu'à sa résolution et le <Suspense> le plus proche affiche son contenu de repli ; si elle est rejetée, c'est l'error boundary la plus proche qui s'affiche.

use peut-il être appelé sous condition ?

Oui. use est le seul hook que vous pouvez appeler dans des instructions if, des boucles et après un return anticipé. Il doit quand même être appelé depuis un composant ou un hook, jamais depuis un gestionnaire d'événement ou un effet.

Pourquoi use(fetch()) dans mon composant boucle-t-il à l'infini ?

Chaque rendu crée une nouvelle promesse. Le composant se suspend dessus, React refait le rendu quand elle se résout, et ce rendu crée une autre promesse en attente. Créez la promesse hors du rendu : dans l'état d'un parent, au niveau du module, dans un cache ou dans un loader de framework.

Peut-on envelopper use dans un try/catch ?

Non. Quand une promesse est en attente, use interrompt le rendu en levant une valeur, et l'intercepter casse ce mécanisme. Gérez une promesse rejetée avec une error boundary, ou attachez .catch() à la promesse pour qu'elle se résolve en une valeur de repli.

Faut-il remplacer la récupération de données dans useEffect par use ?

use a besoin d'une promesse créée une seule fois et réutilisée, ce que vous fournissent les frameworks et les bibliothèques de données. Dans une simple application client, récupérer les données dans un effet (ou avec une bibliothèque comme TanStack Query) reste courant et tout à fait correct.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

COMMENCER