useSyncExternalStore : Synchronisation d'État Externe dans React
Découvrez useSyncExternalStore, le hook React sous-estimé pour synchroniser l'état de React avec des sources externes comme le DOM ou des stores globaux, simplifiant le code et améliorant les performances.
Introduction

useSyncExternalStore est un hook React qui permet à vos composants de s'abonner à une source de données externe et de lire sa valeur. Il est particulièrement utile pour synchroniser l'état de React avec des APIs de navigateur, des éléments DOM ou des stores globaux, offrant une alternative plus propre et plus performante à l'utilisation excessive de useEffect pour ces cas.
Précis de configuration
| Élément | Version / Lien |
|---|---|
| Langage / Runtime | JavaScript / TypeScript, Node.js |
| Librairie principale | React (version 18 ou supérieure) |
| APIs requises | APIs de navigateur (ex: navigator.online, window.addEventListener), Élément HTML dialog |
| Clés / credentials nécessaires | Aucune |
Guide étape par étape

Étape 1 — Suivi du statut en ligne du navigateur
Pourquoi : La propriété navigator.online est une source d'état externe gérée par le navigateur. La synchroniser avec l'état de React via useState et useEffect peut entraîner des désynchronisations si l'état est mis à jour manuellement ailleurs ou si le nettoyage n'est pas géré correctement. useSyncExternalStore garantit que l'état de React est toujours à jour avec la valeur réelle du navigateur.
import { useSyncExternalStore, useState, useEffect } from "react";
// Fonction pour s'abonner aux changements du statut en ligne
function subscribe(callback: () => void) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
// Retourne une fonction de nettoyage pour désabonner les écouteurs d'événements
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}
// Composant principal
export default function App() {
// Utilise useSyncExternalStore pour synchroniser l'état 'isOnline'
// Le premier argument est la fonction 'subscribe'
// Le second argument est la fonction 'getSnapshot' qui retourne la valeur actuelle de l'état externe
const isOnline = useSyncExternalStore(subscribe, () => navigator.online);
return (
<h1 style={{ color: isOnline ? undefined : "red" }}>
{isOnline ? "Online" : "Offline"}
</h1>
);
}
Étape 2 — Synchronisation de l'état d'un élément HTML dialog
Pourquoi : L'élément HTML dialog a son propre état d'ouverture/fermeture, qui peut être modifié par des interactions utilisateur (comme la touche Échap) que React ne gère pas nativement. Tenter de synchroniser cela avec useState peut entraîner un état désynchronisé. useSyncExternalStore permet à React de réagir aux événements natifs du dialog.
import { useSyncExternalStore, useRef } from "react";
export default function App() {
// Référence à l'élément HTMLDialogElement
const modalRef = useRef<HTMLDialogElement>(null);
// Fonction pour s'abonner à l'événement 'toggle' du dialog
function subscribe(cb: () => void) {
// [Note de l'éditeur : modalRef.current doit exister avant d'ajouter l'écouteur]
modalRef.current?.addEventListener("toggle", cb);
return () => {
modalRef.current?.removeEventListener("toggle", cb);
};
}
// Fonction pour obtenir le snapshot de l'état 'open' du dialog
// Le ?? false gère le cas où modalRef.current est null au premier rendu
const getSnapshot = () => modalRef.current?.open ?? false;
// Fonction pour le snapshot côté serveur (optionnel, pour SSR)
// Par défaut, le dialog est considéré comme fermé sur le serveur
const getServerSnapshot = () => false;
// Utilise useSyncExternalStore pour synchroniser l'état 'isOpen'
const isOpen = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
return (
<>
<button onClick={() => {
// [Note de l'éditeur : modalRef.current doit exister avant d'appeler showModal]
modalRef.current?.showModal();
}}>
Open Modal
</button>
<p>{isOpen ? "Opened" : "Closed"}</p>
<dialog ref={modalRef}>
<button onClick={() => {
// [Note de l'éditeur : modalRef.current doit exister avant d'appeler close]
modalRef.current?.close();
}}>
Close
</button>
</dialog>
</>
);
}
Étape 3 — Création d'un store global personnalisé (liste de tâches)
Pourquoi : Pour gérer un état global (comme une liste de tâches) à travers plusieurs composants sans avoir recours au prop drilling ou à des solutions complexes comme le Context API ou Redux, tout en maintenant de bonnes performances. useSyncExternalStore permet de créer un store simple et réactif.
Fichier todoStore.ts :
// todoStore.ts
let todos: string[] = []; // L'état externe réel
const listeners = new Set<() => void>(); // Ensemble de callbacks pour notifier les composants React
export const TodoStore = {
// Retourne un snapshot de l'état actuel des tâches
getTodos(): string[] {
return todos;
},
// Ajoute une tâche et notifie les écouteurs
addTodo(name: string) {
todos = [...todos, name]; // Met à jour l'état de manière immuable
listeners.forEach(cb => cb()); // Appelle tous les écouteurs pour forcer un re-rendu
},
// Supprime une tâche et notifie les écouteurs
removeTodo(index: number) {
todos = todos.toSpliced(index, 1); // Met à jour l'état de manière immuable
listeners.forEach(cb => cb()); // Appelle tous les écouteurs
},
// S'abonne aux changements du store
subscribe(cb: () => void) {
listeners.add(cb);
// Retourne une fonction de nettoyage pour désabonner le callback
return () => {
listeners.delete(cb);
};
}
};
Fichier App.tsx :
import { useSyncExternalStore, useRef } from "react";
import { TodoStore } from "./todoStore"; // Importe le store personnalisé
export default function App() {
// Utilise useSyncExternalStore pour obtenir l'état des tâches depuis le store global
const todos = useSyncExternalStore(
TodoStore.subscribe, // Fonction pour s'abonner
TodoStore.getTodos, // Fonction pour obtenir le snapshot actuel
() => [] // Fonction pour le snapshot côté serveur (état initial vide)
);
const nameRef = useRef<HTMLInputElement>(null);
// Gère la soumission du formulaire pour ajouter une tâche
function onSubmit(e: React.FormEvent) {
e.preventDefault();
if (nameRef.current?.value) {
TodoStore.addTodo(nameRef.current.value);
nameRef.current.value = ""; // Efface l'entrée après ajout
}
}
// Gère la suppression d'une tâche
function removeTodo(index: number) {
TodoStore.removeTodo(index);
}
return (
<form onSubmit={onSubmit}>
<input ref={nameRef} />
<ul>
{todos.map((todo, index) => (
<li key={index} onClick={() => removeTodo(index)}>
{todo}
</li>
))}
</ul>
</form>
);
}
Tableaux comparatifs

| Caractéristique | useSyncExternalStore |
useEffect |
|---|---|---|
| Cas d'usage principal | Synchroniser l'état de React avec des sources de données externes (DOM, APIs navigateur, stores globaux). | Exécuter des effets secondaires après le rendu (abonnements, requêtes de données, manipulation directe du DOM). |
| Gestion de la synchronisation | Gère automatiquement la souscription et la désouscription, garantissant que l'état de React est toujours à jour avec la source externe. | Nécessite une gestion manuelle des abonnements et des nettoyages, sujette aux erreurs de synchronisation et aux fuites de mémoire. |
| Performance | Optimisé pour les mises à jour fréquentes et la prévention des re-rendus inutiles, car il ne re-rend que les composants qui utilisent l'état mis à jour. | Peut entraîner des re-rendus excessifs si les dépendances ne sont pas gérées correctement ou si les effets sont coûteux. |
| Complexité du code | Simplifie le code pour la synchronisation d'état externe en encapsulant la logique de souscription et de snapshot. | Peut devenir complexe avec des logiques de synchronisation et de nettoyage élaborées, nécessitant souvent des AbortController ou des fonctions de nettoyage. |
| Server-Side Rendering (SSR) | Prend en charge un getServerSnapshot optionnel pour fournir un état initial sur le serveur, évitant les problèmes d'hydratation. |
Ne s'exécute pas sur le serveur, ce qui peut entraîner des décalages d'état entre le rendu initial du serveur et l'hydratation du client. |
| Immutabilité | Permet de muter directement l'état externe (si la source le permet) sans enfreindre les règles de React, car React ne gère pas directement l'état mais le lit via getSnapshot. |
Nécessite généralement des mises à jour d'état immuables pour éviter les problèmes de re-rendu et de détection des changements. |
⚠️ Erreurs fréquentes et pièges
- Désynchronisation de l'état React avec la source externe : L'état de React peut ne pas refléter la source externe si celle-ci est modifiée par des moyens non contrôlés par React (ex: un élément DOM natif).
useSyncExternalStorerésout ce problème en écoutant les événements de la source externe et en mettant à jour l'état de React en conséquence. - Oubli du nettoyage des abonnements dans
useEffect: DansuseEffect, il est facile d'oublier de retourner une fonction de nettoyage pour désabonner les écouteurs d'événements, ce qui peut entraîner des fuites de mémoire.useSyncExternalStoreforce la définition d'une fonction de nettoyage dans son argumentsubscribe. - Re-rendus excessifs avec
useEffect: Pour les sources d'état qui changent fréquemment,useEffectpeut déclencher de nombreux re-rendus.useSyncExternalStoreest optimisé pour ces scénarios, ne re-rendant que les composants qui utilisent l'état mis à jour. - Problèmes d'hydratation en Server-Side Rendering (SSR) :
useEffectne s'exécute pas sur le serveur, ce qui peut entraîner un décalage entre le rendu initial du serveur et l'état du client.useSyncExternalStorepermet de spécifier ungetServerSnapshotpour fournir un état initial cohérent sur le serveur.
Glossaire
useSyncExternalStore : Un hook React qui permet aux composants de s'abonner à une source de données externe et de lire sa valeur, garantissant que l'état de React reste synchronisé avec la source.subscribe (fonction) : Une fonction passée à useSyncExternalStore qui prend un callback et renvoie une fonction de nettoyage, utilisée pour s'abonner aux changements de la source de données externe.getSnapshot (fonction) : Une fonction passée à useSyncExternalStore qui renvoie la valeur actuelle de la source de données externe, permettant à React de lire l'état synchronisé.
Points clés à retenir
useSyncExternalStoreest le hook de choix pour synchroniser l'état de React avec des sources externes, qu'il s'agisse d'APIs de navigateur, d'éléments DOM ou de stores globaux personnalisés.- Il simplifie considérablement la gestion des abonnements et des nettoyages par rapport à l'utilisation manuelle de
useEffect. - Il garantit une synchronisation fiable de l'état, même lorsque la source externe est modifiée en dehors du contrôle direct de React.
- Ce hook est optimisé pour la performance, réduisant les re-rendus inutiles pour les états qui changent fréquemment.
- Il offre un support natif pour le Server-Side Rendering (SSR) via la fonction
getServerSnapshot. - Il permet de créer des solutions de gestion d'état global légères et efficaces sans la complexité des bibliothèques tierces ou du Context API de React.
Ressources
- Documentation officielle React: https://react.dev/reference/react/useSyncExternalStore
- Cours gratuit "React Hooks Simplified": https://courses.webdevsimplified.com/courses/react-hooks-simplified
- Vidéo sur l'élément HTML
dialog: https://www.youtube.com/watch?v=y_Y8z_qV20o