perf(Breadcrumbs): derive overflow in render; refresh menu on content change#7999
perf(Breadcrumbs): derive overflow in render; refresh menu on content change#7999Copilot wants to merge 8 commits into
Conversation
🦋 Changeset detectedLatest commit: 99f5bf5 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 |
Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
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.
|
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.
There was a problem hiding this comment.
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/effectiveHideRootduring render from cached measurements. - Restricted width measurement/
ResizeObserverusage tomenu/menu-with-root(skipping work inwrap). - 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
| // 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. |
| let visibleItemsWidthTotal = calculateVisibleItemsWidth(currentVisibleItemWidths) | ||
|
|
||
| if (menuItemCount > 0) { | ||
| visibleItemsWidthTotal += menuButtonWidth | ||
| } | ||
| while ( | ||
| (overflow === 'menu' || overflow === 'menu-with-root') && | ||
| (visibleItemsWidthTotal > availableWidth || currentVisibleItemWidths.length > MIN_VISIBLE_ITEMS) | ||
| ) { |
| const measuredContainerWidth = containerElement.offsetWidth | ||
| setContainerWidth(prev => (prev === measuredContainerWidth ? prev : measuredContainerWidth)) |
- 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
Changelog
Breadcrumbsoverflow 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
wrapoverflow mode now does zero JavaScript measurement — it is laid out entirely by CSS. TheResizeObserverand the layout-effect measurement only run for themenu/menu-with-rootmodes that actually need it.Breadcrumbs.test.tsxcovering same-item-count child updates: open the overflow menu, rerender with updated crumb labels/hrefs, and confirm the menu reflects the new content without akey-based remount.Changed
calculateOverflowderivation inuseMemoinstead of threeuseStatevalues written fromuseEffect/resize handlers.[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
useEffects that synced overflow state (the source of the stale-crumbs bug and the on-mount cascade).key-based remount workaround to refresh stale overflow items.Rollout strategy
Testing & Reviewing
Behavior is unchanged for all existing cases; this is an internal refactor plus the stale-content fix. The full
Breadcrumbssuite passes (29 tests, including the same-count rerender regression test).To verify the original bug is fixed:
Breadcrumbswithoverflow="menu"and 6+ items.keychange).Worth a look in a real browser (non-zero widths) since measurement is layout-driven:
wrapmode wraps natively with no observer attached.menu/menu-with-rootcollapse correctly on resize and at narrow widths, with no flash of all crumbs on mount.Merge checklist