Skip to content

perf(Breadcrumbs): derive overflow in render; refresh menu on content change#7999

Open
Copilot wants to merge 8 commits into
mainfrom
copilot/fix-breadcrumbs-stale-crumbs
Open

perf(Breadcrumbs): derive overflow in render; refresh menu on content change#7999
Copilot wants to merge 8 commits into
mainfrom
copilot/fix-breadcrumbs-stale-crumbs

Conversation

Copilot AI commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Changelog

Breadcrumbs overflow handling has been reworked so the visible/menu split is derived during render from measured widths instead of being stored in state and synced through effects.

This started as a fix for stale overflow items (collapsed crumbs not refreshing when children changed without a change in item count, e.g. after a soft navigation). Deriving in render fixes that class of bug structurally, and along the way removes the render cascade and the flash-of-all-crumbs on mount.

New

  • wrap overflow mode now does zero JavaScript measurement — it is laid out entirely by CSS. The ResizeObserver and the layout-effect measurement only run for the menu / menu-with-root modes that actually need it.
  • Regression test in Breadcrumbs.test.tsx covering same-item-count child updates: open the overflow menu, rerender with updated crumb labels/hrefs, and confirm the menu reflects the new content without a key-based remount.

Changed

  • The visible items, overflow-menu items, and effective-root state are now computed with a pure calculateOverflow derivation in useMemo instead of three useState values written from useEffect/resize handlers.
  • Width measurement moved to a layout effect keyed on [childArray, overflow]; resize only updates a single cached container width, and the derivation reuses the cached per-item widths (no DOM reads on resize).

Removed

  • The init/recompute useEffects that synced overflow state (the source of the stale-crumbs bug and the on-mount cascade).
  • The implicit need for a downstream key-based remount workaround to refresh stale overflow items.

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

Behavior is unchanged for all existing cases; this is an internal refactor plus the stale-content fix. The full Breadcrumbs suite passes (29 tests, including the same-count rerender regression test).

To verify the original bug is fixed:

  • Render Breadcrumbs with overflow="menu" and 6+ items.
  • Open the overflow menu and note a collapsed crumb's label.
  • Rerender with the same number of items but different collapsed crumb labels/hrefs (no key change).
  • Reopen the overflow menu and confirm the updated labels show and the stale ones are gone.

Worth a look in a real browser (non-zero widths) since measurement is layout-driven:

  • wrap mode wraps natively with no observer attached.
  • menu / menu-with-root collapse correctly on resize and at narrow widths, with no flash of all crumbs on mount.

Merge checklist

@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 99f5bf5

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

Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix stale crumbs in Breadcrumbs component with overflow menu Breadcrumbs: refresh menu overflow items when children content changes Jun 16, 2026
Copilot AI requested a review from mattcosta7 June 16, 2026 02:41
Replaces the effect-synced visible/menu state with a pure render-time derivation, and skips all measurement (layout effect + ResizeObserver) in wrap mode since it is laid out entirely by CSS. This also fixes stale overflow items when children change without a count change (supersedes the prior effect-guard fix) and removes the flash-of-all-crumbs on mount.
@mattcosta7 mattcosta7 changed the title Breadcrumbs: refresh menu overflow items when children content changes perf(Breadcrumbs): derive overflow in render; refresh menu on content change Jun 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Action required

👋 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. Check the integration testing docs for step-by-step instructions. Or, apply the integration-tests: skipped manually label to skip these checks.

To publish a canary release for integration testing, apply the Canary Release label 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 Jun 16, 2026
@github-actions github-actions Bot temporarily deployed to storybook-preview-7999 June 16, 2026 12:41 Inactive
calculateOverflow now returns a primitive menu-item count + flag instead of new visible/menu element arrays. finalChildren slices the stable childArray and is memoized on those primitives, so a resize that does not cross a collapse threshold no longer rebuilds the <li>/separator list every frame.
@mattcosta7 mattcosta7 marked this pull request as ready for review June 16, 2026 13:07
@mattcosta7 mattcosta7 requested a review from a team as a code owner June 16, 2026 13:07
@mattcosta7 mattcosta7 requested review from Copilot and jonrohan June 16, 2026 13:07
@github-actions github-actions Bot temporarily deployed to storybook-preview-7999 June 16, 2026 13:11 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors Breadcrumbs overflow handling so the visible/menu split is derived during render from measured widths, avoiding state/effect syncing and ensuring overflow menu content stays up to date when children change (even with the same item count).

Changes:

  • Reworked overflow logic to compute menuItemCount/effectiveHideRoot during render from cached measurements.
  • Restricted width measurement/ResizeObserver usage to menu / menu-with-root (skipping work in wrap).
  • Added a regression test for same-count child updates and included a patch changeset.
Show a summary per file
File Description
packages/react/src/Breadcrumbs/Breadcrumbs.tsx Refactors overflow to a pure derivation + layout measurement, and gates observation/measurement by overflow mode.
packages/react/src/Breadcrumbs/tests/Breadcrumbs.test.tsx Adds coverage for updating overflow menu items when children change without changing item count.
.changeset/breadcrumbs-overflow-refresh.md Patch changeset describing the overflow refresh/perf improvement.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 5

Comment on lines +158 to +162
// overflow menu, given the measured widths. Returns counts/flags (primitives)
// rather than element arrays so the render memo below only invalidates when the
// split actually changes, not on every sub-threshold resize. Kept outside the
// overflow menu, given the measured widths. Kept outside the component so it has no
// hidden dependency on previous state and can be called directly during render.
Comment on lines +199 to +207
let visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItemWidths)

if (menuItemCount > 0) {
visibleItemsWidthTotal += menuButtonWidth
}
while (
(overflow === 'menu' || overflow === 'menu-with-root') &&
(visibleItemsWidthTotal > availableWidth || currentVisibleItemWidths.length > MIN_VISIBLE_ITEMS)
) {
Comment on lines +266 to +267
const measuredContainerWidth = containerElement.offsetWidth
setContainerWidth(prev => (prev === measuredContainerWidth ? prev : measuredContainerWidth))
Comment thread packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx Outdated
Comment thread packages/react/src/Breadcrumbs/__tests__/Breadcrumbs.test.tsx Outdated
- Remove dead menuItemCount>0 preamble in calculateOverflow (always false there)
- Round ResizeObserver contentRect.width to match integer offsetWidth and avoid a needless mount re-render
- Assert details container non-null before within() instead of casting as HTMLElement
…tional

- Fix garbled/duplicated calculateOverflow doc comment
- Drop always-true in-loop menuItemCount>0 guard; fold menuButtonWidth into the total
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.

5 participants