Skip to content

perf(TreeView): defer scrollIntoView to coalesce reflows during rapid navigation#7545

Open
hectahertz wants to merge 2 commits intomainfrom
hectahertz/perf-treeview-defer-scroll
Open

perf(TreeView): defer scrollIntoView to coalesce reflows during rapid navigation#7545
hectahertz wants to merge 2 commits intomainfrom
hectahertz/perf-treeview-defer-scroll

Conversation

@hectahertz
Copy link
Contributor

@hectahertz hectahertz commented Feb 14, 2026

Closes #

Defers scrollIntoView calls in TreeView's onFocus handler using requestAnimationFrame, so rapid keyboard navigation (held arrow key) coalesces multiple scroll requests into a single reflow per frame instead of one per keystroke.

Previously, every focus change called scrollIntoView synchronously, forcing the browser to compute layout for the entire DOM. With large trees (26K items, 289K DOM nodes), this cost ~3.7ms per call, adding up to 164ms of blocked main thread time across 50 rapid keypresses.

Each onFocus now schedules a scroll via rAF. If a new focus event fires before the frame callback runs, the previous one is cancelled. Only the final focused element's scroll actually executes. Exposed to Item components through RootContext.

This preserves the existing behavior for single-focus scenarios (tab into tree, click, single arrow press). The scroll still happens, just one frame later.

Benchmarks (26,000 items, 289K DOM nodes)

scrollIntoView cost per call:

Approach Avg Max
Sync (old) 3.72ms 4.80ms
rAF schedule (new) 0.01ms 0.10ms

50 rapid focus changes (held key simulation):

Metric Sync (old) rAF coalesced (new)
Total time 164ms 0.1ms
Per-call cost 3.28ms 0.002ms
Actual scrollIntoView calls 50 1 (deferred)
Reflows forced 50 1

Changelog

New

N/A

Changed

  • scrollIntoView in TreeView item onFocus is now deferred via requestAnimationFrame, coalescing multiple scroll requests during rapid keyboard navigation into a single reflow per frame

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 51 existing TreeView tests pass unchanged
  • To manually verify: open the StressTest story, expand several directories, and hold ArrowDown. Focus should scroll smoothly without jank.

Merge checklist

  • Added/updated tests
  • Added/updated documentation
  • Added/updated previews (Storybook)
  • Changes are SSR compatible
  • Tested in Chrome
  • Tested in Firefox
  • Tested in Safari
  • Tested in Edge
  • (GitHub staff only) Integration tests pass at github/github

@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

🦋 Changeset detected

Latest commit: a0368de

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 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
@github-actions
Copy link
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.

Copy link
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 improves TreeView keyboard-navigation performance by deferring scrollIntoView work to requestAnimationFrame, so multiple rapid focus changes coalesce into a single scroll/reflow per frame.

Changes:

  • Added a scrollElementIntoView helper on RootContext that schedules (and cancels) scrollIntoView via requestAnimationFrame.
  • Updated TreeView item onFocus handling to use the deferred scroll helper instead of calling scrollIntoView synchronously.
  • Added a patch changeset documenting the perf change.

Reviewed changes

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

File Description
packages/react/src/TreeView/TreeView.tsx Defers and coalesces focus-driven scrolling via rAF and exposes the helper through RootContext.
.changeset/treeview-defer-scroll.md Adds a patch changeset entry describing the TreeView perf improvement.

Comment on lines +148 to +152
pendingScrollRef.current = null
element.scrollIntoView({block: 'nearest', inline: 'nearest'})
})
}, [])

Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

scrollElementIntoView schedules a rAF callback but there’s no unmount cleanup. If the TreeView unmounts (or items are removed) before the frame fires, the callback can still run and call scrollIntoView on a detached element. Consider adding a useEffect cleanup that cancels any pending animation frame (and clears the ref), and optionally guarding the callback with if (!element.isConnected) return before calling scrollIntoView.

Suggested change
pendingScrollRef.current = null
element.scrollIntoView({block: 'nearest', inline: 'nearest'})
})
}, [])
pendingScrollRef.current = null
// Guard against calling scrollIntoView on a detached element
if (!('isConnected' in element) || !(element as Element & {isConnected: boolean}).isConnected) {
return
}
element.scrollIntoView({block: 'nearest', inline: 'nearest'})
})
}, [])
useEffect(() => {
return () => {
if (pendingScrollRef.current !== null) {
cancelAnimationFrame(pendingScrollRef.current)
pendingScrollRef.current = null
}
}
}, [])

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

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

@primer-integration
Copy link

Integration test results from github/github-ui:

Passed  CI   Passed
Passed  VRT   Passed
Passed  Projects   Passed

All checks passed!

@hectahertz
Copy link
Contributor Author

Friendly ping for review: @jonrohan @siddharthkp @TylerJDev 🙏

Copy link
Member

@jonrohan jonrohan left a comment

Choose a reason for hiding this comment

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

Seems ok to me, but don't feel like I know a ton about react animation frames.

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.

3 participants