UseSyncExternalStore pozwala komponentowi odczytywać dane znajdujące się poza Reactem bez ryzyka wystąpienia błędów wizualnych, które może wprowadzić renderowanie współbieżne (concurrent rendering). Zastępuje on stary wzorzec wprowadzania zewnętrznych wartości do lokalnego stanu za pomocą useEffect – wzorzec ten może powodować tzw. „rozwarstwienie” (tearing), czyli sytuację, w której części interfejsu użytkownika wyświetlają różne wartości podczas tego samego renderowania.
Dlaczego stary wzorzec zawodzi w trybie współbieżnym
Przed React 18 powszechnym rozwiązaniem było:
const [value, setValue] = useState(initial);
useEffect(() => {
const unsubscribe = externalSource.subscribe(v => setValue(v));
return unsubscribe;
}, []);
Efekt uruchamia się po wyrenderowaniu komponentu. Gdy współbieżny renderer Reacta zdecyduje się wstrzymać, przerwać lub powtórzyć renderowanie, efekt może zostać wywołany w innym momencie niż interfejs użytkownika odczytujący stan. Wynikiem jest niespójność: jeden komponent może odczytać starą wartość, inny nową, a ekran przez chwilę wyświetla nieprawidłowe dane. Takie „rozwarstwienie” (tearing) jest nieszkodliwe w czysto synchronicznym świecie, ale staje się widocznym błędem, gdy React renderuje wiele wersji interfejsu równolegle.
Jak useSyncExternalStore rozwiązuje ten problem
Hook ten wymaga dwóch funkcji zwrotnych (callbacks):
- subscribe – rejestruje subskrybenta i zwraca funkcję, która go usuwa.
- getSnapshot – zwraca aktualną wartość zewnętrznego źródła.
React wywołuje subscribe podczas montowania komponentu i usuwa go podczas odmontowywania. Podczas każdego renderowania wywołuje getSnapshot i gwarantuje, że wszystkie komponenty korzystające z tego samego zewnętrznego magazynu (store) odczytają dokładnie ten sam snapshot. Ponieważ odczyt następuje w fazie renderowania, renderer może koordynować aktualizacje w całym drzewie, eliminując problem rozwarstwienia.
Biblioteki takie jak Zustand i Redux już teraz opakowują swoje magazyny w ten hook, dzięki czemu działają one natywnie z funkcjami współbieżnymi React 18.
Zasady zapobiegające nieprawidłowemu działaniu hooka
- Zwracaj tylko to, czego potrzebuje komponent – duży obiekt zmusza Reacta do porównywania głębokich struktur przy każdej aktualizacji. Wąski snapshot sprawia, że wykrywanie zmian jest wydajne.
- Zachowaj tożsamość referencyjną – jeśli zewnętrzna wartość się nie zmieniła, getSnapshot musi zwrócić tę samą referencję do obiektu. Zwracanie nowego obiektu przy każdym wywołaniu powoduje nieskończoną pętlę renderowania, ponieważ React zauważa zmianę przy każdym cyklu.
- Ogranicz użycie do źródeł zewnętrznych – lokalny stan komponentu powinien znajdować się w
useStatelubuseReducer. Używanie hooka do zewnętrznego magazynu dla czysto wewnętrznych wartości wprowadza niepotrzebną pośredniość.
Kiedy sięgać po useSyncExternalStore
- Opakowywanie API przeglądarki (rozmiar okna, status sieci, media queries).
- Łączenie zwykłego magazynu JavaScript, który nie zna Reacta.
- Zastępowanie prop-drillingu globalnym źródłem typu read-only, do którego subskrybuje się wiele komponentów.
Kontrargument
Wzorzec useState + useEffect wciąż działa w przypadku prostych, rzadko zmieniających się danych, zwłaszcza gdy aplikacja nie ma włączonych funkcji współbieżnych. Jest to podejście niższego poziomu, które niektórym programistom wydaje się łatwiejsze do zrozumienia, ponieważ logika subskrypcji znajduje się wyraźnie w efekcie. Jednak ceną za to jest ryzyko wystąpienia rozwarstwienia po włączeniu renderowania współbieżnego. W projektach korzystających już z nowych możliwości React 18, dodatkowe bezpieczeństwo zapewniane przez useSyncExternalStore przeważa nad minimalnym narzutem związanym z dodatkowym kodem (boilerplate).
Na co zwrócić uwagę w następnej kolejności
- Zweryfikuj, czy getSnapshot Twojego magazynu zwraca stabilną referencję; szybkie
console.logreferencji podczas developmentu może ujawnić przypadkowe tworzenie nowych obiektów. - Dbaj o to, aby funkcja subskrypcji była lekka – ciężkie operacje powinny odbywać się wewnątrz zewnętrznego źródła, a nie w callbacku przekazywanym do Reacta.
- Testuj komponenty w trybie współbieżnym Reacta (np. używając
actzReactDOM.createRoot), aby potwierdzić, że nie pojawia się rozwarstwienie.
Żywe demo pokazuje działanie hooka, synchronizując prosty licznik przechowywany poza Reactem i aktualizując wiele komponentów bez błędów wizualnych. Demo jest dostępne pod adresem https://usesyncexternalstore.vercel.app/. Kod źródłowy znajduje się na GitHubie, a dłuższy artykuł wyjaśnia szczegóły implementacji.
Podsumowanie: W świecie, w którym React może renderować części interfejsu poza kolejnością, useSyncExternalStore jest niezawodnym mostem między zewnętrznymi danymi a renderowaniem komponentów. Zapewnia spójność wizualną, skaluje się do globalnych magazynów i jest już wbudowany w popularne biblioteki do zarządzania stanem. Stosuj go wszędzie tam, gdzie musisz odczytywać stan spoza Reacta, i dbaj o to, aby snapshoty były małe i stabilne, aby w pełni korzystać z jego zalet.
