Skip to content

perf(useIsMacOS): replace useState+useEffect with useSyncExternalStore#7548

Merged
hectahertz merged 2 commits intomainfrom
hectahertz/perf-useismacOS-sync-external-store
Feb 18, 2026
Merged

perf(useIsMacOS): replace useState+useEffect with useSyncExternalStore#7548
hectahertz merged 2 commits intomainfrom
hectahertz/perf-useismacOS-sync-external-store

Conversation

@hectahertz
Copy link
Copy Markdown
Contributor

@hectahertz hectahertz commented Feb 14, 2026

Closes #

useIsMacOS used useState + useEffect to detect the platform in an SSR-safe way. The useState initializer already computed the correct value on the client, but the useEffect unconditionally called setIsMacOS with the same value, scheduling a second render pass on every mount.

Replaced with useSyncExternalStore, the idiomatic React 18+ API for values that differ between server and client. During hydration, React handles the server/client mismatch internally in a single synchronous pass, no extra render needed.

Eliminates a guaranteed double-render on every mount for all consumers: Tooltip, KeybindingHint, and TreeView. The impact scales with the number of children, since the unnecessary re-render cascades to all of them.

Measurements

Measured on the TreeView stress test (1000 tree items) as worst case. 2 runs each, reload-based traces, no CPU/network throttling.

Metric Before (main) After (PR) Delta
LCP (median) ✨ 2,416ms 2,264ms -152ms (-6.3%)
Render Delay (median) 2,216ms 2,095ms -121ms (-5.5%)
Individual runs

Before (main):

  • Run 1: LCP 2,421ms (TTFB 206ms, Render Delay 2,215ms)
  • Run 2: LCP 2,410ms (TTFB 192ms, Render Delay 2,217ms)

After (PR):

  • Run 1: LCP 2,268ms (TTFB 155ms, Render Delay 2,113ms)
  • Run 2: LCP 2,259ms (TTFB 182ms, Render Delay 2,077ms)

TreeView shows the largest improvement because a single useIsMacOS call at the root triggers a re-render that cascades across 1000 child items. Tooltip and KeybindingHint will see smaller absolute gains but every instance still saves one full render cycle on mount.

Changelog

New

N/A

Changed

  • useIsMacOS now uses useSyncExternalStore instead of useState + useEffect, eliminating an unnecessary re-render on every mount

Removed

N/A

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

All 3 consumer test suites pass (89 tests total):

  • TooltipV2/__tests__/Tooltip.test.tsx
  • KeybindingHint/KeybindingHint.test.tsx
  • TreeView/TreeView.test.tsx

To verify the double render is eliminated:

  1. Add a console.log('render') inside the Tooltip component
  2. Render a Tooltip in a test/story
  3. Before: logs "render" twice on mount
  4. After: logs "render" once

Merge checklist

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 14, 2026

🦋 Changeset detected

Latest commit: 3b97134

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Or, apply the integration-tests: skipped manually label to skip these checks.

@github-actions github-actions bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Feb 14, 2026
@hectahertz hectahertz force-pushed the hectahertz/perf-useismacOS-sync-external-store branch from 04b72c7 to 7223e52 Compare February 14, 2026 14:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the SSR-safe useIsMacOS hook by switching it to React 18’s useSyncExternalStore, removing an unconditional mount-time state update that caused an extra render pass in all consumers (e.g. Tooltip, KeybindingHint, TreeView).

Changes:

  • Replaced useState + useEffect in useIsMacOS with useSyncExternalStore to avoid a guaranteed second render on mount.
  • Added a changeset for a patch release describing the performance improvement.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
packages/react/src/hooks/useIsMacOS.ts Refactors platform detection to useSyncExternalStore to avoid unnecessary mount re-renders while staying SSR-safe.
.changeset/perf-useismacOS-sync-external-store.md Adds a patch changeset documenting the perf change for release notes.

Comment on lines 28 to 30
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)
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useIsMacOS is a publicly exported hook and its behavior around SSR/hydration is subtle. There are hook unit tests for other hooks under packages/react/src/hooks/__tests__, but none for this one—please add a small useIsMacOS test that at least verifies SSR defaults to false (e.g. via react-dom/server), and client behavior can be controlled via a mocked @primer/behaviors/utils implementation.

Copilot uses AI. Check for mistakes.
@primer-integration
Copy link
Copy Markdown

👋 Hi from github/github-ui! Your integration PR is ready: https://github.com/github/github-ui/pull/13848

@primer-integration
Copy link
Copy Markdown

Integration test results from github/github-ui:

Passed  CI   Passed
Waiting  VRT   Waiting
Passed  Projects   Passed

@hectahertz hectahertz added this pull request to the merge queue Feb 18, 2026
Merged via the queue into main with commit d4b32c3 Feb 18, 2026
62 checks passed
@hectahertz hectahertz deleted the hectahertz/perf-useismacOS-sync-external-store branch February 18, 2026 16:20
This was referenced Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants