Skip to content

Commit 3793bd9

Browse files
committed
fix: defer the update in useIsSSR to avoid interrupting hydration
1 parent 661ba8e commit 3793bd9

File tree

1 file changed

+31
-14
lines changed

1 file changed

+31
-14
lines changed

packages/@react-aria/ssr/src/SSRProvider.tsx

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,7 @@ function useModernSSRSafeId(defaultId?: string): string {
168168
export const useSSRSafeId: typeof useModernSSRSafeId | typeof useLegacySSRSafeId = typeof React['useId'] === 'function' ? useModernSSRSafeId : useLegacySSRSafeId;
169169

170170
function getSnapshot() {
171-
return false;
172-
}
173-
174-
function getServerSnapshot() {
175-
return true;
171+
return null;
176172
}
177173

178174
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -181,17 +177,38 @@ function subscribe(onStoreChange: () => void): () => void {
181177
return () => {};
182178
}
183179

180+
function useModernIsSSR(): boolean {
181+
let initialState = false;
182+
// In React 18, we can use useSyncExternalStore to detect if we're server rendering or hydrating.
183+
React.useSyncExternalStore(subscribe, getSnapshot, () => {
184+
initialState = true;
185+
186+
// Important! We must return the same value for both server and client to avoid unnecessary render
187+
return getSnapshot();
188+
});
189+
let [isSSR, setIsSSR] = useState(initialState);
190+
191+
useLayoutEffect(() => {
192+
if (!isSSR) {
193+
return;
194+
}
195+
196+
React.startTransition(() => {
197+
setIsSSR(false);
198+
});
199+
}, [isSSR]);
200+
201+
return isSSR;
202+
}
203+
204+
function useLegacyIsSSR(): boolean {
205+
return useContext(IsSSRContext);
206+
}
207+
208+
// Use React.useSyncExternalStore and React.startTransition in React >=18 if available, otherwise fall back to our old implementation.
184209
/**
185210
* Returns whether the component is currently being server side rendered or
186211
* hydrated on the client. Can be used to delay browser-specific rendering
187212
* until after hydration.
188213
*/
189-
export function useIsSSR(): boolean {
190-
// In React 18, we can use useSyncExternalStore to detect if we're server rendering or hydrating.
191-
if (typeof React['useSyncExternalStore'] === 'function') {
192-
return React['useSyncExternalStore'](subscribe, getSnapshot, getServerSnapshot);
193-
}
194-
195-
// eslint-disable-next-line react-hooks/rules-of-hooks
196-
return useContext(IsSSRContext);
197-
}
214+
export const useIsSSR: () => boolean = typeof React['startTransition'] === 'function' ? useModernIsSSR : useLegacyIsSSR;

0 commit comments

Comments
 (0)