useTransition est un Hook React qui vous permet de mettre Ă  jour l’état sans bloquer l’UI.

const [isPending, startTransition] = useTransition()

Référence

useTransition()

Appelez useTransition au niveau racine de votre composant pour marquer certaines mises Ă  jour d’état comme Ă©tant des Transitions.

import { useTransition } from 'react';

function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}

Voir d’autres exemples ci-dessous.

ParamĂštres

useTransition ne prend aucun argument.

Valeur renvoyée

useTransition renvoie un tableau avec exactement deux Ă©lĂ©ments :

  1. Le drapeau isPending qui vous indique si la Transition est en cours.
  2. La fonction startTransition qui vous permet de marquer une mise Ă  jour d’état comme Transition.

La fonction startTransition

La fonction startTransition renvoyĂ©e par useTransition vous permet de marquer une mise Ă  jour d’état comme Ă©tant une Transition.

function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');

function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}

ParamĂštres

Valeur renvoyée

startTransition ne renvoie rien.

Limitations et points Ă  noter

  • useTransition est un Hook, il ne peut donc ĂȘtre appelĂ© qu’au sein de composants ou de Hooks personnalisĂ©s. Si vous avez besoin de dĂ©marrer une Transition Ă  un autre endroit (par exemple, depuis une bibliothĂšque de gestion de donnĂ©es), utilisez plutĂŽt la fonction autonome startTransition.

  • Vous pouvez enrober une mise Ă  jour dans une Transition uniquement si vous avez accĂšs Ă  la fonction set de l’état en question. Si vous souhaitez dĂ©marrer une Transition en rĂ©action Ă  une prop ou Ă  la valeur renvoyĂ©e par un Hook personnalisĂ©, utilisez plutĂŽt useDeferredValue.

  • La fonction que vous passez Ă  startTransition doit ĂȘtre synchrone. React exĂ©cute cette fonction immĂ©diatement, et marque toutes les mises Ă  jour demandĂ©es lors de son exĂ©cution comme des Transitions. Si vous essayez de faire des mises Ă  jour d’état plus tard (par exemple avec un timer), elles ne seront pas marquĂ©es comme des Transitions.

  • La fonction startTransition a une identitĂ© stable, elle ne figure donc gĂ©nĂ©ralement pas dans les dĂ©pendances des Effets, mais l’inclure n’entraĂźnera pas un dĂ©clenchement d’Effet superflu. Si le linter vous permet de l’omettre sans erreurs, c’est que cette omission est sans danger. Apprenez-en davantage sur l’allĂšgement des dĂ©pendances d’Effets

  • Une mise Ă  jour d’état marquĂ©e comme une Transition pourra ĂȘtre interrompue par d’autres mises Ă  jour d’état. Par exemple, si vous mettez Ă  jour un composant de graphe au sein d’une Transition, mais commencez alors une saisie dans un champ texte tandis que le graphe est en train de refaire son rendu, React redĂ©marrera le rendu du composant graphe aprĂšs avoir traitĂ© la mise Ă  jour d’état du champ.

  • Les mises Ă  jour en Transition ne peuvent pas ĂȘtre utilisĂ©es pour contrĂŽler des champs textuels.

  • Si plusieurs Transitions sont en cours, React les regroupe pour le moment. Cette limitation sera sans doute levĂ©e dans une future version.


Utilisation

Marquer une mise Ă  jour d’état comme Ă©tant une Transition non bloquante

Appelez useTransition au niveau racine de votre composant pour marquer des mises Ă  jour d’état comme Ă©tant des Transitions non bloquantes.

import { useState, useTransition } from 'react';

function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}

useTransition renvoie un tableau avec exactement deux Ă©lĂ©ments :

  1. Le drapeau isPending qui vous indique si la Transition est en cours.
  2. La fonction startTransition qui vous permet de marquer une mise Ă  jour d’état comme Transition.

Vous pouvez marquer une mise Ă  jour d’état comme Ă©tant une Transition de la façon suivante :

function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');

function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}

Les Transitions vous permettent de conserver la rĂ©activitĂ© des mises Ă  jour d’interface utilisateur, mĂȘme sur des appareils lents.

Avec une Transition, votre UI reste rĂ©active pendant le rendu. Par exemple, si l’utilisateur clique sur un onglet mais ensuite change d’avis et va sur un autre onglet, il peut le faire sans devoir d’abord attendre que le premier onglet ait fini son rendu.

La diffĂ©rence entre useTransition et des mises Ă  jour d’état classiques

Exemple 1 sur 2 Â·
Changer l’onglet actif au sein d’une Transition

Dans cet exemple, l’onglet « Articles Â» est artificiellement ralenti pour que son rendu prenne au moins une seconde.

Cliquez sur « Articles Â» puis cliquez immĂ©diatement sur « Contact Â». Remarquez que ça interrompt le rendu lent d’« Articles Â». L’onglet « Contact Â» est affichĂ© immĂ©diatement. Puisque la mise Ă  jour d’état est marquĂ©e comme une Transition, un rendu lent ne gĂšle pas pour autant l’interface utilisateur.

import { useState, useTransition } from 'react';
import TabButton from './TabButton.js';
import AboutTab from './AboutTab.js';
import PostsTab from './PostsTab.js';
import ContactTab from './ContactTab.js';

export default function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }

  return (
    <>
      <TabButton
        isActive={tab === 'about'}
        onClick={() => selectTab('about')}
      >
        À propos
      </TabButton>
      <TabButton
        isActive={tab === 'posts'}
        onClick={() => selectTab('posts')}
      >
        Articles (lent)
      </TabButton>
      <TabButton
        isActive={tab === 'contact'}
        onClick={() => selectTab('contact')}
      >
        Contact
      </TabButton>
      <hr />
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}
      {tab === 'contact' && <ContactTab />}
    </>
  );
}


Mettre Ă  jour le composant parent dans une Transition

Vous pouvez tout aussi bien mettre Ă  jour l’état du composant parent depuis un appel Ă  useTransition. Par exemple, le composant TabButton enrobe la logique de son onClick avec une Transition :

export default function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
startTransition(() => {
onClick();
});
}}>
{children}
</button>
);
}

Puisque le composant parent met Ă  jour son Ă©tat au sein du gestionnaire d’évĂ©nement onClick, cette mise Ă  jour d’état sera marquĂ©e comme Ă©tant une Transition. C’est pourquoi, comme dans l’exemple prĂ©cĂ©dent, vous pouvez cliquer sur « Articles Â» puis immĂ©diatement sur « Contact Â». Le changement d’onglet est marquĂ© comme Ă©tant une Transition : il ne bloque donc pas les interactions utilisateur.

import { useTransition } from 'react';

export default function TabButton({ children, isActive, onClick }) {
  const [isPending, startTransition] = useTransition();
  if (isActive) {
    return <b>{children}</b>
  }
  return (
    <button onClick={() => {
      startTransition(() => {
        onClick();
      });
    }}>
      {children}
    </button>
  );
}


Afficher une indication visuelle pendant la Transition

Vous pouvez utiliser la valeur boolĂ©enne isPending renvoyĂ©e par useTransition pour indiquer Ă  l’utilisateur qu’une Transition est en cours. Par exemple, le bouton d’onglet peut avoir un Ă©tat visuel spĂ©cial « en cours Â» :

function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
// ...
if (isPending) {
return <b className="pending">{children}</b>;
}
// ...

Remarquez que le clic sur « Articles Â» semble dĂ©sormais plus rĂ©actif parce que le bouton d’onglet lui-mĂȘme se met Ă  jour immĂ©diatement :

import { useTransition } from 'react';

export default function TabButton({ children, isActive, onClick }) {
  const [isPending, startTransition] = useTransition();
  if (isActive) {
    return <b>{children}</b>
  }
  if (isPending) {
    return <b className="pending">{children}</b>;
  }
  return (
    <button onClick={() => {
      startTransition(() => {
        onClick();
      });
    }}>
      {children}
    </button>
  );
}


EmpĂȘcher les indicateurs de chargement indĂ©sirables

Dans cet exemple, le composant PostsTab charge des donnĂ©es en utilisant une source de donnĂ©es compatible Suspense. Lorsque vous cliquez sur l’onglet « Articles Â», le composant PostsTab suspend, entraĂźnant l’affichage du plus proche contenu de secours :

import { Suspense, useState } from 'react';
import TabButton from './TabButton.js';
import AboutTab from './AboutTab.js';
import PostsTab from './PostsTab.js';
import ContactTab from './ContactTab.js';

export default function TabContainer() {
  const [tab, setTab] = useState('about');
  return (
    <Suspense fallback={<h1>🌀 Chargement...</h1>}>
      <TabButton
        isActive={tab === 'about'}
        onClick={() => setTab('about')}
      >
        À propos
      </TabButton>
      <TabButton
        isActive={tab === 'posts'}
        onClick={() => setTab('posts')}
      >
        Articles
      </TabButton>
      <TabButton
        isActive={tab === 'contact'}
        onClick={() => setTab('contact')}
      >
        Contact
      </TabButton>
      <hr />
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}
      {tab === 'contact' && <ContactTab />}
    </Suspense>
  );
}

Masquer le conteneur d’onglets dans son intĂ©gralitĂ© pour afficher un indicateur de chargement entraĂźne une expĂ©rience utilisateur dĂ©sagrĂ©able. Si vous ajoutez useTransition Ă  TabButton, vous pouvez plutĂŽt manifester l’attente en cours dans le bouton d’onglet.

Remarquez que cliquer sur « Articles Â» ne remplace plus l’ensemble du conteneur d’onglets avec un spinner :

import { useTransition } from 'react';

export default function TabButton({ children, isActive, onClick }) {
  const [isPending, startTransition] = useTransition();
  if (isActive) {
    return <b>{children}</b>
  }
  if (isPending) {
    return <b className="pending">{children}</b>;
  }
  return (
    <button onClick={() => {
      startTransition(() => {
        onClick();
      });
    }}>
      {children}
    </button>
  );
}

Apprenez-en davantage sur l’utilisation des Transitions avec Suspense.

Remarque

Les Transitions « n’attendront Â» que le temps nĂ©cessaire pour Ă©viter de masquer du contenu dĂ©jĂ  rĂ©vĂ©lĂ© (comme le conteneur d’onglets). Si l’onglet Articles avait un pĂ©rimĂštre <Suspense> imbriquĂ©, la Transition « n’attendrait Â» pas ce dernier.


Construire un routeur compatible Suspense

Si vous construisez un framework React ou un routeur, nous vous recommandons de marquer toutes les navigations de pages comme étant des Transitions.

function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();

function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...

Nous recommandons ça pour deux raisons :

Voici un petit exemple de routeur trÚs simplifié utilisant les Transitions pour ses navigations.

import { Suspense, useState, useTransition } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    <Suspense fallback={<BigSpinner />}>
      <Router />
    </Suspense>
  );
}

function Router() {
  const [page, setPage] = useState('/');
  const [isPending, startTransition] = useTransition();

  function navigate(url) {
    startTransition(() => {
      setPage(url);
    });
  }

  let content;
  if (page === '/') {
    content = (
      <IndexPage navigate={navigate} />
    );
  } else if (page === '/the-beatles') {
    content = (
      <ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      />
    );
  }
  return (
    <Layout isPending={isPending}>
      {content}
    </Layout>
  );
}

function BigSpinner() {
  return <h2>🌀 Chargement...</h2>;
}

Remarque

Les routeurs compatibles Suspense sont censés enrober par défaut leurs mises à jour de navigation dans des Transitions.


Afficher une erreur Ă  l’utilisateur avec un pĂ©rimĂštre d’erreur

Canary (fonctionnalité expérimentale)

Les pĂ©rimĂštres d’erreurs pour useTransition ne sont actuellement disponibles que sur les canaux de livraison Canary et ExpĂ©rimental de React. Apprenez-en davantage sur les canaux de livraison React.

Si une fonction passĂ©e Ă  startTransition lĂšve une erreur, vous pouvez afficher l’erreur Ă  votre utilisateur au moyen d’un pĂ©rimĂštre d’erreur. Pour utiliser un pĂ©rimĂštre d’erreur, enrobez le composant qui appelle useTransition avec ce pĂ©rimĂštre. Lorsque la fonction passĂ©e Ă  startTransition lĂšvera une erreur, le contenu de secours du pĂ©rimĂštre d’erreur sera affichĂ©.

import { useTransition } from "react";
import { ErrorBoundary } from "react-error-boundary";

export function AddCommentContainer() {
  return (
    <ErrorBoundary fallback={<p>⚠ Ça sent le pĂąté </p>}>
      <AddCommentButton />
    </ErrorBoundary>
  );
}

function addComment(comment) {
  // Pour les besoins de la démonstration uniquement
  if(comment == null) {
    throw Error('Example Error: An error thrown to trigger error boundary')
  }
}

function AddCommentButton() {
  const [pending, startTransition] = useTransition();

  return (
    <button
      disabled={pending}
      onClick={() => {
        startTransition(() => {
          // On ne passe volontairement pas de commentaire
          // afin d’entraüner une erreur.
          addComment();
        });
      }}
    >
      Ajouter un commentaire
    </button>
  );
}


Dépannage

Mettre Ă  jour un champ depuis une Transition ne fonctionne pas

Vous ne pouvez pas utiliser une Transition pour mettre Ă  jour une variable d’état qui contrĂŽle un champ :

const [text, setText] = useState('');
// ...
function handleChange(e) {
// ❌ Les Transitions ne peuvent enrober des mises Ă  jour d'Ă©tat qui contrĂŽlent des champs
startTransition(() => {
setText(e.target.value);
});
}
// ...
return <input value={text} onChange={handleChange} />;

C’est parce que les Transitions sont non bloquantes, alors que la mise Ă  jour d’un champ en rĂ©action Ă  un Ă©vĂ©nement de modification doit survenir de façon synchrone. Si vous souhaitez exĂ©cuter une Transition en rĂ©ponse Ă  une saisie, vous avez deux options :

  1. Vous pouvez dĂ©clarer deux variables d’état distinctes : une pour l’état du champ (qui sera toujours mise Ă  jour de façon synchrone), et une que vous mettrez Ă  jour au sein d’une Transition. Ça vous permet de contrĂŽler le champ avec l’état synchrone, tout en passant la variable d’état en Transition (qui est susceptible de « retarder Â» par rapport Ă  la saisie) au reste de votre logique de rendu.
  2. Sinon, vous pouvez n’avoir qu’une variable d’état et utiliser useDeferredValue qui vous permettra d’ĂȘtre « en retard Â» sur la vĂ©ritable valeur. Ça dĂ©clenchera automatiquement des rendus non bloquants pour « rattraper Â» la nouvelle valeur.

React ne traite pas ma mise Ă  jour d’état comme Ă©tant une Transition

Lorsque vous enrobez une mise Ă  jour d’état dans une Transition, assurez-vous qu’elle survient effectivement pendant l’appel Ă  startTransition :

startTransition(() => {
// ✅ L’état est mis Ă  jour *pendant* l’appel Ă  startTransition
setPage('/about');
});

La fonction que vous passez Ă  startTransition doit ĂȘtre synchrone.

Vous ne pouvez pas marquer une mise Ă  jour comme Ă©tant une Transition avec ce genre de code :

startTransition(() => {
// ❌ L’état est mis Ă  jour *aprĂšs* l’appel Ă  startTransition
setTimeout(() => {
setPage('/about');
}, 1000);
});

Faites plutĂŽt ceci :

setTimeout(() => {
startTransition(() => {
// ✅ L’état est mis Ă  jour *pendant* l’appel Ă  startTransition
setPage('/about');
});
}, 1000);

Dans le mĂȘme esprit, vous ne pouvez pas marquer une mise Ă  jour comme Ă©tant une Transition avec du code ressemblant Ă  ça :

startTransition(async () => {
await someAsyncFunction();
// ❌ L’état est mis Ă  jour *aprĂšs* l’appel Ă  startTransition
setPage('/about');
});

En revanche, ce type de code fonctionne :

await someAsyncFunction();
startTransition(() => {
// ✅ L’état est mis Ă  jour *pendant* l’appel Ă  startTransition
setPage('/about');
});

Je veux appeler useTransition ailleurs que dans un composant

Vous ne pouvez pas appeler useTransition hors d’un composant parce que c’est un Hook. Pour ce type de besoin, prĂ©fĂ©rez la fonction autonome startTransition. Son fonctionnement est identique, Ă  ceci prĂšs qu’elle ne fournit pas l’indicateur isPending.


La fonction que je passe à startTransition est exécutée immédiatement

Si vous exĂ©cutez ce code, ça affichera 1, 2, 3 :

console.log(1);
startTransition(() => {
console.log(2);
setPage('/about');
});
console.log(3);

C’est censĂ© afficher 1, 2, 3. La fonction que vous passez Ă  startTransition ne doit pas ĂȘtre diffĂ©rĂ©e. Contrairement au setTimeout du navigateur, la fonction de rappel n’est pas appelĂ©e plus tard. React exĂ©cute votre fonction immĂ©diatement, mais les mises Ă  jour d’état que vous y demandez pendant son exĂ©cution sont marquĂ©es comme Ă©tant des Transitions. Vous pouvez vous imaginer le fonctionnement suivant :

// Version simplifiée du fonctionnement de React

let isInsideTransition = false;

function startTransition(scope) {
isInsideTransition = true;
scope();
isInsideTransition = false;
}

function setState() {
if (isInsideTransition) {
// ... planifie une mise Ă  jour d’état en tant que Transition ...
} else {
// ... planifie une mise Ă  jour d’état urgente ...
}
}