useSyncExternalStore permet à un composant de lire des données situées en dehors de React sans risquer les problèmes d'affichage (glitches visuels) que le rendu concurrent peut introduire. Il remplace l'ancien modèle consistant à extraire des valeurs externes dans un état local via useEffect, un modèle qui peut provoquer du « tearing » – des parties de l'interface utilisateur affichant des valeurs différentes lors d'un même rendu.
Pourquoi l'ancien modèle échoue lors du rendu concurrent
Avant React 18, la recette courante était :
const [value, setValue] = useState(initial);
useEffect(() => {
const unsubscribe = externalSource.subscribe(v => setValue(v));
return unsubscribe;
}, []);
L'effet s'exécute après le rendu du composant. Lorsque le moteur de rendu concurrent de React décide de mettre en pause, d'annuler ou de rejouer un rendu, l'effet peut se déclencher à un moment différent de l'interface utilisateur qui lit l'état. Le résultat est une incohérence : un composant peut avoir lu l'ancienne valeur, un autre la nouvelle, et l'écran affiche brièvement des données incohérentes. Ce « tearing » est inoffensif dans un monde purement synchrone, mais devient un bug visible lorsque React génère plusieurs versions de l'interface utilisateur en parallèle.
Comment useSyncExternalStore résout le problème
Le hook nécessite deux fonctions de rappel (callbacks) :
- subscribe – enregistre un écouteur et renvoie une fonction pour le supprimer.
- getSnapshot – renvoie la valeur actuelle de la source externe.
React appelle subscribe lors du montage du composant et le nettoie lors du démontage. À chaque rendu, il invoque getSnapshot et garantit que tous les composants utilisant le même store externe lisent exactement le même instantané (snapshot). Comme la lecture a lieu pendant la phase de rendu, le moteur de rendu peut coordonner les mises à jour sur l'ensemble de l'arbre, éliminant ainsi le tearing.
Des bibliothèques telles que Zustand et Redux enveloppent déjà leurs stores avec ce hook, c'est pourquoi elles fonctionnent nativement avec les fonctionnalités concurrentes de React 18.
Règles pour éviter les mauvais comportements du hook
- Ne retournez que ce dont le composant a besoin – un objet volumineux force React à comparer des structures profondes à chaque mise à jour. Un snapshot restreint permet de garder la détection de changement peu coûteuse.
- Préservez l'identité référentielle – si la valeur externe n'a pas changé, getSnapshot doit renvoyer la même référence d'objet. Retourner un nouvel objet à chaque appel déclenche une boucle de rendu infinie car React perçoit un changement à chaque rendu.
- Limitez l'usage aux sources externes – l'état local du composant doit appartenir à
useStateouuseReducer. Utiliser le hook de store externe pour des valeurs purement internes ajoute une indirection inutile.
Quand utiliser useSyncExternalStore
- Envelopper des API de navigateur (taille de la fenêtre, statut de connexion réseau, media queries).
- Connecter un store JavaScript classique qui ne connaît pas React.
- Remplacer le « prop-drilling » par une source globale en lecture seule à laquelle de nombreux composants s'abonnent.
L'argument opposé
Le modèle useState + useEffect fonctionne toujours pour des données simples et changeant peu fréquemment, surtout lorsqu'une application n'active pas les fonctionnalités concurrentes. C'est une approche de plus bas niveau que certains développeurs trouvent plus facile à appréhender car la logique d'abonnement réside explicitement dans l'effet. Cependant, le compromis est le risque de tearing une fois le rendu concurrent activé. Pour les projets utilisant déjà les nouvelles capacités de React 18, la sécurité supplémentaire de useSyncExternalStore l'emporte sur le léger surplus de code (boilerplate).
Points de vigilance
- Vérifiez que le getSnapshot de votre store renvoie une référence stable ; un simple
console.logde la référence pendant le développement peut révéler une recréation accidentelle d'objet. - Gardez la fonction d'abonnement légère – le travail lourd doit s'effectuer à l'intérieur de la source externe, et non dans le callback passé à React.
- Testez les composants en mode concurrent de React (par exemple, en utilisant
actavecReactDOM.createRoot) pour confirmer qu'aucun tearing n'apparaît.
Une démo en direct montre le hook en action, synchronisant un simple compteur stocké en dehors de React et mettant à jour plusieurs composants sans glitchs visuels. La démo est disponible à l'adresse https://usesyncexternalstore.vercel.app/. Le code source est disponible sur GitHub, et un article plus détaillé explique les détails de l'implémentation.
À retenir : Dans un monde où React peut rendre des parties de l'interface utilisateur dans le désordre, useSyncExternalStore est le pont fiable entre les données externes et le rendu des composants. Il préserve la cohérence visuelle, s'adapte aux stores globaux et est déjà intégré dans les bibliothèques de gestion d'état populaires. Adoptez-le partout où vous devez lire un état non-React, et gardez vos snapshots petits et stables pour en tirer pleinement profit.
