Underline nav shared observer coalesced rebuild#8035
Conversation
🦋 Changeset detectedLatest commit: d436177 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 |
13d8541 to
3d4beb8
Compare
|
…nObserver for overflow detection Replace per-item IntersectionObserver + offsetTop reads with one shared, root-scoped observer per registry and microtask-coalesced rebuilds. Eliminates forced reflow and observer churn during resize. No public API changes.
3d4beb8 to
d436177
Compare
There was a problem hiding this comment.
Pull request overview
This PR optimizes overflow detection for UnderlineNav and ActionBar by moving from per-item overflow work (multiple IntersectionObserver instances plus layout reads) to a single shared, root-scoped IntersectionObserver per component, and by coalescing registry rebuilds to reduce redundant work during resize/wrap events.
Changes:
- Extend
createDescendantRegistrywith an opt-in “overflow” mode that provides a shared observer (useRegisterOverflowObserver) and microtask-coalesced rebuild scheduling. - Update
UnderlineNavandActionBaritems to use the shared overflow observer and pass a clipping root ref to the registry provider. - Add a patch changeset describing the internal performance improvement.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/utils/descendant-registry.tsx | Adds shared overflow observer support and coalesced rebuild scheduling to the descendant registry utility. |
| packages/react/src/UnderlineNav/UnderlineNavItemsRegistry.ts | Enables the registry’s overflow mode for UnderlineNav items. |
| packages/react/src/UnderlineNav/UnderlineNavItem.tsx | Switches item overflow detection to the registry’s shared observer hook. |
| packages/react/src/UnderlineNav/UnderlineNav.tsx | Passes the clipping root ref into the UnderlineNav registry provider. |
| packages/react/src/ActionBar/ActionBar.tsx | Enables overflow mode for ActionBar registry and switches items to shared observer-based overflow detection. |
| .changeset/underline-nav-shared-overflow-observer.md | Adds a patch changeset for the internal performance optimization. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 3
- Review effort level: Low
| if (!supportsIntersectionObserver()) return null | ||
| if (rootRef && rootRef.current === null) return null | ||
|
|
||
| const root = rootRef?.current ?? null | ||
| if (observerRef.current && observerRootRef.current === root) return observerRef.current |
| const scheduleRebuild = useCallback(() => { | ||
| if (pendingRebuildRef.current) return | ||
| pendingRebuildRef.current = true | ||
| queueMicrotask(() => { | ||
| pendingRebuildRef.current = false | ||
| // Guard against dispatching into an unmounted provider. | ||
| if (isMountedRef.current) rebuildRegistry() | ||
| }) | ||
| }, []) |
| /** | ||
| * Subscribe an element to the registry's shared, root-scoped IntersectionObserver and derive whether it is currently | ||
| * overflowing from the observed entry. | ||
| * | ||
| * This requires the registry to be created with the `overflow` option so a shared observer is configured. When no | ||
| * shared observer is configured the hook is inert and always reports `false`. | ||
| * | ||
| * @param ref Ref to the element whose overflow state should be tracked. | ||
| * @param options.disabled When true, skips observer subscription entirely and always reports `false`. Useful for | ||
| * items whose overflow is determined by an ancestor (e.g. ActionBar items inside an overflowing group). | ||
| */ | ||
| function useRegisterOverflowObserver(ref: RefObject<HTMLElement | null>, options?: {disabled?: boolean}) { |
Closes #
Follow-up performance work on top of the merged
UnderlineNavrewrite (#7506) andActionBaroverflow handling. Replaces the per-item overflow detection (oneIntersectionObserverper item plus per-itemoffsetTopreads) with a single shared, root-scopedIntersectionObserverper component, and coalesces same-frame registry rebuilds into one microtask-scheduled update.This is internal-only: no public API changes.
Why
ref.current.offsetTopinsideuseSyncExternalStore's snapshot, forcing a synchronous layout per item. Overflow is now derived fromIntersectionObserverentries off the render path; snapshots read a cached boolean.queueMicrotaskso a resize that wraps several items triggers a single rebuild.Changelog
Changed
UnderlineNavandActionBarnow detect item overflow using a single shared root-scopedIntersectionObserverper component instead of one observer per item.createDescendantRegistrygains an opt-inoverflowoption exposinguseRegisterOverflowObserver, plus microtask-coalesced rebuilds.Removed
IntersectionObserver+offsetTopoverflow checks inUnderlineNav.ItemandActionBaritems.Rollout strategy
Testing & Reviewing
No behavioral changes are intended. Existing
ActionBar,UnderlineNav, anddescendant-registryunit tests pass. Verify overflow behavior by resizing the viewport: items wrap into the overflow menu and the "More" affordance appears/disappears as before, and overflowed items remain non-focusable (tabIndex={-1}/aria-hidden).Merge checklist