Mẫu useState + useEffect đọc nguồn ngoài có hai vấn đề.
Tearing — với concurrent rendering, React có thể tạm dừng giữa chừng việc render. Nếu nguồn ngoài đổi giá trị trong lúc đó, phần cây render trước và phần render sau đọc hai giá trị khác nhau, UI hiển thị không nhất quán.
Lệch một khung hình khi hydrate — effect chỉ chạy sau lần render đầu, nên màn hình hiện giá trị mặc định rồi mới nhảy sang giá trị thật.
useSyncExternalStore nhận đúng ba mảnh để React tự quản lý việc này:
const isDark = useSyncExternalStore(
(onChange) => { // subscribe
const mq = window.matchMedia('(prefers-color-scheme: dark)')
mq.addEventListener('change', onChange)
return () => mq.removeEventListener('change', onChange)
},
() => window.matchMedia('(prefers-color-scheme: dark)').matches, // client snapshot
() => false, // server snapshot
)React đọc lại snapshot ở thời điểm an toàn và ép render đồng bộ khi phát hiện lệch, nên không còn tearing. getServerSnapshot bắt buộc có khi dùng SSR — thiếu nó React ném lỗi vì không biết render gì trên server.
Ràng buộc quan trọng: getSnapshot phải trả về giá trị tham chiếu ổn định khi dữ liệu chưa đổi. Trả về object mới mỗi lần gọi sẽ gây vòng lặp render vô hạn. Đây là hook mà các thư viện state (Zustand, Redux) dùng bên dưới, ứng dụng thường không gọi trực tiếp.