diff --git a/.changeset/perf-useismacOS-sync-external-store.md b/.changeset/perf-useismacOS-sync-external-store.md new file mode 100644 index 00000000000..6b999df419a --- /dev/null +++ b/.changeset/perf-useismacOS-sync-external-store.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +perf(useIsMacOS): replace useState+useEffect with useSyncExternalStore to eliminate unnecessary re-render diff --git a/packages/react/src/hooks/useIsMacOS.ts b/packages/react/src/hooks/useIsMacOS.ts index 190ee7af1b2..262d217b94b 100644 --- a/packages/react/src/hooks/useIsMacOS.ts +++ b/packages/react/src/hooks/useIsMacOS.ts @@ -1,19 +1,30 @@ import {isMacOS as ssrUnsafeIsMacOS} from '@primer/behaviors/utils' -import {useEffect, useState} from 'react' -import {canUseDOM} from '../utils/environment' +import {useSyncExternalStore} from 'react' + +// No-op. The platform never changes at runtime, so there is nothing to +// subscribe to. Hoisted to avoid creating a new function on every call. +const subscribe = () => () => {} + +// Safe default for SSR since we can't detect the platform on the server. +const getServerSnapshot = () => false /** - * SSR-safe hook for determining if the current platform is MacOS. When rendering - * server-side, will default to non-MacOS and then re-render in an effect if the - * client turns out to be a MacOS device. + * SSR-safe hook for determining if the current platform is MacOS. + * + * Uses `useSyncExternalStore` to read the platform value: + * + * - On the **client**, `ssrUnsafeIsMacOS` reads `navigator.userAgent` and + * returns the real value immediately, with no extra render pass. + * + * - On the **server**, returns `false`. During hydration, if the snapshots + * differ, React handles the mismatch internally in a single synchronous + * pass, avoiding the layout shift that a deferred `useEffect` + `setState` + * would cause. + * + * Previous implementation used `useState` + `useEffect`, which caused an + * unconditional second render on every mount (even on the client where the + * initial value was already correct). */ export function useIsMacOS() { - const [isMacOS, setIsMacOS] = useState(() => (canUseDOM ? ssrUnsafeIsMacOS() : false)) - - useEffect(() => { - // eslint-disable-next-line react-hooks/set-state-in-effect - setIsMacOS(ssrUnsafeIsMacOS()) - }, []) - - return isMacOS + return useSyncExternalStore(subscribe, ssrUnsafeIsMacOS, getServerSnapshot) }