ITADN

Incorrect usage of `useSyncExternalStore` results in Hydration errors with SSR.

#38OpenLattyware 创建于 2025-08-23
L
Lattywarecommented
The current implementation gets the current value of the store for the third argument to `useSyncExternalStore`, which should instead get the *initial* value of the store. This breaks hydration when the current store value doesn't match the value used on the server during SSR. From [the docs](https://react.dev/reference/react/useSyncExternalStore#usesyncexternalstore): > **optional** `getServerSnapshot`: A function that returns the initial snapshot of the data in the store. It will be used only during server rendering and during hydration of server-rendered content on the client. The server snapshot must be the same between the client and the server, and is usually serialized and passed from the server to the client. If you omit this argument, rendering the component on the server will throw an error. As an example of a failure we can take a store that contains the timezone: you render on the server using a default timezone, then on the client you set the timezone using browser APIs to the user's local one. If hydration occurs *after* doing this, the current implementation will use the local timezone, breaking if it differs from the server's timezone as the hydration result is different to the server rendered version. Instead, the first render for hydration should get given that initial server timezone value, and then be rerendered with the client value after that, which is what happens if you provide that initial value to `useSyncExternalStore`. My current replacement is therefore as follows, although this is obviously specific to my use case and not general, and changes the API in a significant way. ```ts import { atom } from "nanostores"; import { type RefObject, useCallback, useRef, useSyncExternalStore, } from "react"; const emit = <Value>(snapshotRef: RefObject<Value>, onChange: () => void) => (value: Value) => { if (snapshotRef.current !== value) { snapshotRef.current = value; onChange(); } }; export const store = <Value>(initialValue: Value) => { const store = atom(initialValue); return [ store, () => { const snapshotRef = useRef<Value>(store.get()); const subscribe = useCallback((onChange: () => void) => { const update = emit(snapshotRef, onChange); update(store.value); return store.listen(update); }, []); return useSyncExternalStore( subscribe, () => snapshotRef.current, () => initialValue, ); }, ] as const; }; ```
2 条评论