Fix usePaneWidth triggering unnecessary React re-renders on every window resize#7824
Fix usePaneWidth triggering unnecessary React re-renders on every window resize#7824
usePaneWidth triggering unnecessary React re-renders on every window resize#7824Conversation
…h to skip unnecessary re-renders
🦋 Changeset detectedLatest commit: b43e27c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
Fix `usePaneWidth` triggering unnecessary React re-renders on every window resize
There was a problem hiding this comment.
Pull request overview
This PR addresses a performance issue in usePaneWidth where startTransition was being invoked on every window.resize tick even when the computed max pane width hadn’t changed, causing unnecessary concurrent React renders in SplitPageLayout/PageLayout scenarios.
Changes:
- Added a
maxPaneWidthRefand guarded thestartTransitionstate update so it only runs whenactualMaxchanges or when the current width must be clamped. - Added a unit test intended to cover the “skip redundant transition” behavior.
- Added a Storybook performance test story to help visually confirm resize-triggered re-render behavior.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/PageLayout/usePaneWidth.ts | Adds ref-based guarding to avoid redundant startTransition updates during resize while keeping DOM/CSS/ARIA updates unconditional. |
| packages/react/src/PageLayout/usePaneWidth.test.ts | Adds a unit test for the new guard behavior (but currently doesn’t assert render/transition skipping directly). |
| packages/react/src/PageLayout/PageLayout.performance.stories.tsx | Adds a Storybook “Re-render Counter (Issue #7801)” story to manually verify resize re-render behavior in DevTools. |
| .changeset/silent-taxis-eat.md | Patch changeset entry for the performance fix. |
Copilot's findings
- Files reviewed: 4/4 changed files
- Comments generated: 2
| // maxPaneWidth should be unchanged — no re-render triggered | ||
| expect(result.current.maxPaneWidth).toBe(renderCountBefore) | ||
|
|
| // Now resize again to a different width that produces the SAME max | ||
| // 900px -> 900px (no actual viewport change, same max = 389) | ||
| const renderCountBefore = result.current.maxPaneWidth | ||
| window.dispatchEvent(new Event('resize')) | ||
| await act(async () => { | ||
| await vi.runAllTimersAsync() | ||
| }) | ||
|
|
||
| // maxPaneWidth should be unchanged — no re-render triggered | ||
| expect(result.current.maxPaneWidth).toBe(renderCountBefore) |
Closes #7801
Overview
When
SplitPageLayouthas a resizable pane (resizable={true}), theusePaneWidthhook callsstartTransition(() => setMaxPaneWidth(actualMax))insidesyncAll()on every window resize event — even whenactualMaxhasn't changed. Unlike normalsetState,startTransitiondoes not bail out on same-value updates, so this triggers unnecessary React concurrent renders on every resize tick (~60/sec during a drag).On pages with many components consuming the layout context (e.g. GitHub PR conversation tab), this produces ~11 React scheduler calls totaling ~108ms in a single animation frame with CPU throttle, causing visible jank.
Fix: Added a
maxPaneWidthRefto track the previousmaxPaneWidthvalue, and guarded thestartTransitioncall so it only fires whenactualMaxhas actually changed or when the current width needs to be clamped. The CSS variable updates, ref updates, and ARIA updates (which don't cause React re-renders) remain unconditional.Changelog
New
Re-render Counter (Issue #7801)) to help verify resize re-render behavior with React DevTools Profiler.Changed
usePaneWidth:syncAll()now skipsstartTransitionwhen the computed max pane width hasn't changed, avoiding unnecessary React re-renders on window resize.Removed
None
Rollout strategy
Testing & Reviewing
usePaneWidthtriggers unnecessary React re-renders on every window resize #7801)Unit test added:
should skip startTransition when maxPaneWidth has not changed (#7801)inusePaneWidth.test.tsMerge checklist