perf: derive Autocomplete re-sort and LabelGroup truncation during render#8005
perf: derive Autocomplete re-sort and LabelGroup truncation during render#8005mattcosta7 with Copilot wants to merge 24 commits into
Conversation
Rewrite four effects flagged by react-hooks/set-state-in-effect to derive state during render or via useSyncExternalStore: - useMedia: use useSyncExternalStore (tear-free, no redundant initial render) - ToggleSwitch: hide loading label via render-time derivation; keep only the delayed reveal in an effect - SelectPanel: reset intermediateSelected by tracking previous open state - ValidationAnimationContainer: mount via render-time derivation Also make the useMedia matchMedia mock reflect real MediaQueryList behavior (matches updates on change).
Account for every remaining react-hooks/set-state-in-effect suppression in component/hook source. These are necessary effects (committed-DOM reads or consumer side effects), so add an inline rationale to each rather than a bare disable: - TreeView: getAccessibleName reads committed DOM - LabelGroup: hideChildrenAfterIndex reads DOM children (numeric branch is a possible follow-up derivation) - AutocompleteMenu: sort-on-close shares the effect with onOpenChange - Dialog: commit-timing focus hack - SelectPanel2: reads label textContent from DOM - SelectPanel: no-items announce (commit timing) and first-open fetch (callback) Demo/test suppressions (stories, storybook data) are left for separate cleanup.
Resolve conflicts from Add eslint-plugin-react-you-might-not-need-an-effect (#8000): - Keep useSyncExternalStore implementation in useMedia.ts (our refactor) - Keep render-time derivation in ValidationAnimationContainer.tsx (our refactor) - Keep render-time derivation in SelectPanel.tsx intermediateSelected (our refactor) - Keep render-time derivation in ToggleSwitch.tsx; add no-event-handler suppression - Merge descriptive comments with new react-you-might-not-need-an-effect suppressions in Dialog.tsx, AutocompleteMenu.tsx, SelectPanel.tsx, and TreeView.tsx Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
🦋 Changeset detectedLatest commit: 702f528 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>
Co-authored-by: mattcosta7 <8616962+mattcosta7@users.noreply.github.com>
|
There was a problem hiding this comment.
Pull request overview
This PR is a follow-up refactor to #8001 that narrows two previously “splittable later” effect sites by deriving state during render for derivable transitions (while keeping real side effects in effects). The focus is Autocomplete close-time reordering and LabelGroup numeric truncation.
Changes:
- AutocompleteMenu: Track
showMenutransitions during render and trigger close-time re-sorting on the open→closed transition; keeponOpenChangein an effect. - LabelGroup: For numeric
visibleChildCount, derive the truncationvisibilityMapfrom child indices (not committed DOM) and add a shallow equality guard; keep'auto'truncation effect-driven viaIntersectionObserver. - Tests: Strengthen coverage for
onOpenChange(false → true → false)and add rerender coverage for numeric LabelGroup truncation updates.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/LabelGroup/LabelGroup.tsx | Derives numeric truncation visibility from child indices and updates render path to use a cached childArray. |
| packages/react/src/LabelGroup/LabelGroup.test.tsx | Adds rerender assertions to ensure numeric truncation updates correctly when visibleChildCount changes. |
| packages/react/src/Autocomplete/AutocompleteMenu.tsx | Moves close-time sorting to a render-time transition check while keeping onOpenChange effect-driven. |
| packages/react/src/Autocomplete/Autocomplete.test.tsx | Tightens onOpenChange assertions to validate open/close sequencing. |
Copilot's findings
- Files reviewed: 4/4 changed files
- Comments generated: 2
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…derive-state-from-effects
…; address review feedback
- AutocompleteMenu remounts on open/close (Overlay vs VisuallyHidden parent),
so the prevShowMenu render transition never fired and the re-sort was dead
code. Derive the sorted order during render while the menu is closed instead.
- LabelGroup: align numeric visibility guard with existing truthiness checks so
visibleChildCount={0} no longer causes redundant render-phase updates.
- Add changeset for the Autocomplete/LabelGroup render-time derivation.
…t during client hydration; keep SelectPanel intermediate-reset as render-time state (ref writes disallowed in render)
…derive-state-from-effects
…derive-state-from-effects
…derive-state-from-effects
…-state-from-effects
Retargets base to main and drops the old #8001 leftovers (useMedia, SelectPanel, ToggleSwitch, ValidationAnimationContainer, Dialog, TreeView, SelectPanel2), which are handled by their own focused PRs. Keeps only the Autocomplete close-time re-sort and LabelGroup numeric-truncation render derivations.
|
Rescoped this to a standalone PR: retargeted the base from |
Standalone performance refactor (originally split out of #8001). It narrows two mixed-responsibility effects so derivable state updates happen during render, while genuine side effects stay in effects. Behavior-preserving: no API or visual changes.
Changelog
New
Changed
AutocompleteMenuonOpenChangeeffect.showMenu === false), so it never runs on the frequent renders that happen while the menu is open.AutocompleteOverlayremounts this component on each open/close, so a render-time previous-value transition can't be observed here — guarding on the closed state is what replaces the old close-time effect.onOpenChangeremains inuseEffectas the actual side effect.LabelGroupvisibleChildCounttruncation now derivesvisibilityMapfrom child indices during render instead of reading committed DOM state in an effect.'auto'truncation remains effect-driven viaIntersectionObserver.Tests
Autocompletecoverage to assertonOpenChangefires correctly across open/close transitions.LabelGrouprerender coverage for numeric truncation updates.Close-time sort, derived during render while the menu is closed:
Removed
LabelGroup.set-state-in-effect/ derived-state lint suppressions around Autocomplete close-time sorting.Rollout strategy
Testing & Reviewing
Review by change site:
onOpenChangestill fires with the current open state and remains effect-driven.visibleChildCounttruncation updates correctly across rerenders, and that'auto'truncation, inline expand/collapse, and overlay overflow behavior are unchanged.Validated with:
npx vitest run Autocomplete/Autocomplete.test LabelGroup/LabelGroup.test(40 passing)npx eslint packages/react/src/Autocomplete/AutocompleteMenu.tsx packages/react/src/LabelGroup/LabelGroup.tsx packages/react/src/Autocomplete/Autocomplete.test.tsx packages/react/src/LabelGroup/LabelGroup.test.tsxMerge checklist