useTransition
useTransition
est un Hook React qui vous permet de mettre Ă jour lâĂ©tat sans bloquer lâUI.
const [isPending, startTransition] = useTransition()
- Référence
- Utilisation
- Marquer une mise Ă jour dâĂ©tat comme Ă©tant une Transition non bloquante
- Mettre Ă jour le composant parent dans une Transition
- Afficher une indication visuelle pendant la Transition
- EmpĂȘcher les indicateurs de chargement indĂ©sirables
- Construire un routeur compatible Suspense
- Afficher une erreur Ă lâutilisateur avec un pĂ©rimĂštre dâerreur
- Dépannage
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 :
- Le drapeau
isPending
qui vous indique si la Transition est en cours. - 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
scope
: une fonction qui met Ă jour lâĂ©tat en appelant au moins une fonctionset
. React appelle immédiatementscope
sans argument et marque toutes les mises Ă jour dâĂ©tat demandĂ©es durant lâexĂ©cution synchrone descope
comme des Transitions. Elles seront non bloquantes et nâafficheront pas dâindicateurs de chargement indĂ©sirables.
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 autonomestartTransition
. -
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ĂŽtuseDeferredValue
. -
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 :
- Le drapeau
isPending
qui vous indique si la Transition est en cours. - 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.
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.
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 :
- Les Transitions sont interruptibles, ce qui permet Ă lâutilisateur de cliquer pour aller ailleurs sans devoir attendre la fin du rendu de son premier choix.
- Les Transitions évitent les indicateurs de chargement indésirables, ce qui vous évite de produire des « clignotements » désagréables lors de la navigation.
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>; }
Afficher une erreur Ă lâutilisateur avec un pĂ©rimĂštre dâerreur
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 :
- 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.
- 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 ...
}
}