Skip to content

[autocomplete] Fix popper rendering issues#48327

Merged
mj12albert merged 3 commits into
mui:masterfrom
mj12albert:autocomplete/popper-rendering-improvements-5of5
Apr 21, 2026
Merged

[autocomplete] Fix popper rendering issues#48327
mj12albert merged 3 commits into
mui:masterfrom
mj12albert:autocomplete/popper-rendering-improvements-5of5

Conversation

@mj12albert
Copy link
Copy Markdown
Member

@mj12albert mj12albert commented Apr 19, 2026

Fixes #27670
Fixes #36304
Fixes #40843

Before: https://stackblitz.com/edit/mgl7u76l?file=src%2FDemo.tsx
After: https://stackblitz.com/edit/mgl7u76l-mtyiy8wd?file=src%2FDemo.tsx

1. Popper width tracks anchor resize (#27670)

  1. Click the input to open the popup
  2. Drag the resize handle in the bottom-right of the dashed box to resize

Current(master): popper doesn't resize
Fixed: popper resizes with the anchor (the textbox)


2. Options visibility during exit transition (#36304)

  1. Click the input to open popup
  2. Wait for entering transition to end (3 seconds)
  3. Click outside the popup to close the popup

Current(master): shows "no options" during the exit transition
Fixed: options remain visible/mounted during the exit transition


3. Empty popper when freeSolo (#40843)

  1. Click the input to open popup, the popup has a red border
  2. Type a non-matching value like xyz

Current(master): shows an empty popper, red border still visible
Fixed: popper doesn't render at all when no matches

@mj12albert mj12albert added type: bug It doesn't behave as expected. scope: autocomplete Changes related to the autocomplete. This includes ComboBox. labels Apr 19, 2026
@code-infra-dashboard
Copy link
Copy Markdown

code-infra-dashboard Bot commented Apr 19, 2026

Bundle size

Bundle Parsed size Gzip size
@mui/material 🔺+524B(+0.10%) 🔺+184B(+0.13%)
@mui/lab 0B(0.00%) 0B(0.00%)
@mui/private-theming 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 🔺+111B(+0.74%) 🔺+32B(+0.55%)

Details of bundle changes

Deploy preview

https://deploy-preview-48327--material-ui.netlify.app/


Check out the code infra dashboard for more information about this PR.

@mj12albert mj12albert force-pushed the autocomplete/popper-rendering-improvements-5of5 branch 3 times, most recently from eab775a to 502e987 Compare April 19, 2026 10:22
@mj12albert mj12albert marked this pull request as ready for review April 19, 2026 10:40
@mj12albert mj12albert force-pushed the autocomplete/popper-rendering-improvements-5of5 branch from 502e987 to 65b298a Compare April 20, 2026 13:52
@mj12albert mj12albert force-pushed the autocomplete/popper-rendering-improvements-5of5 branch from 65b298a to eb2b456 Compare April 20, 2026 14:20
Copy link
Copy Markdown
Member

@michelengelen michelengelen left a comment

Choose a reason for hiding this comment

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

PR #48327 — [Autocomplete] Fix popper rendering issues

Author: mj12albert | Requested reviewers: LukasTy, ZeeshanTamboli, michelengelen, silviuaavram, siriwatknp


Overview

Fixes three independent but related Autocomplete Popper bugs:

  1. #27670 — Popper width doesn't follow anchor resize → ResizeObserver + force re-render
  2. #36304 — "No options" flash during exit transition → stale options ref preserved while closing
  3. #40843 — Empty Popper in freeSolo mode when no matches → hasPopupContent guard

New Utility: useForcedRerendering

Clean, minimal implementation. setState({}) pattern is idiomatic for forced re-renders. 'use client' directive and unstable_ export prefix both follow conventions.


Fix 1: ResizeObserver for width tracking

  React.useEffect(() => {
    if (!popupOpen || !anchorEl || typeof ResizeObserver === 'undefined') { ... }
    let lastWidth = anchorEl.clientWidth;
    const observer = new ResizeObserver(() => {
      const newWidth = anchorEl.clientWidth;
      if (lastWidth !== newWidth) { lastWidth = newWidth; forceRenderOnResize(); }
    });
    ...
  }, [popupOpen, anchorEl, forceRenderOnResize]);
  • Observer only active when popupOpen && anchorEl — no unnecessary overhead when closed
  • The lastWidth guard prevents re-renders for height-only changes — good optimization
  • SSR-safe (typeof ResizeObserver === 'undefined' check)
  • Proper cleanup

Minor concern: Width is read from anchorEl.clientWidth during render (a synchronous layout read). This is fine since re-renders are only triggered on actual width changes, but worth
noting.


Fix 2: Exit transition options preservation

  const previousGroupedOptionsRef = React.useRef([]);
  const prevPopupOpenRef = React.useRef(false);
  if (popupOpen && !prevPopupOpenRef.current) {
    previousGroupedOptionsRef.current = [];   // reset on new open
  }
  prevPopupOpenRef.current = popupOpen;
  if (popupOpen && groupedOptions.length > 0) {
    previousGroupedOptionsRef.current = groupedOptions;  // cache while open+populated
  }
  const renderedOptions = popupOpen ? groupedOptions : previousGroupedOptionsRef.current;

The logic is correct and well-commented. The pointerEvents: 'none' guard preventing interaction with stale options is a necessary safety net.

Potential concern with concurrent mode: Ref mutations during render are technically unsafe in concurrent React (renders can be discarded before committing). However, this is an
established pattern in MUI's own usePreviousProps, so it's an accepted trade-off. The logic is also idempotent enough that extra renders don't corrupt state.

Edge case handled well: The test "should not show stale options from a prior session" explicitly covers the scenario where options change between open/close cycles — good catch.


Fix 3: hasPopupContent guard

  const hasPopupContent =
    renderedOptions.length > 0 || loading || !freeSolo || popperProps.keepMounted === true;

Correctly handles all four cases. The comment explains the keepMounted contract clearly. Using renderedOptions (not groupedOptions) here ensures the Popper stays mounted during exit
transitions.

One question: What happens with keepMounted during the exit transition when freeSolo=true and no matches? hasPopupContent is true (due to keepMounted), the Popper renders,
pointerEvents: 'none' is set, but the Paper content is empty. Is that intentional? The Popper would be in the DOM but visually empty — which seems fine since keepMounted is an
explicit consumer opt-in.


Test Coverage

Strong coverage across all three fixes:

  • freeSolo no-match → no Popper render
  • freeSolo + loading → loading text shown
  • keepMounted both object and callback slotProps forms
  • Exit transition DOM preservation (browser-only, correctly uses it.skipIf(isJsdom))
  • Stale options cleared on re-open with changed options prop
  • pointerEvents: 'none' applied when closing
  • ResizeObserver observe/disconnect lifecycle

Gaps:

  • No test for ResizeObserver actually causing a measurable width update on the Popper element (but this would require a real layout engine)
  • No test covering anchorEl changing while popup is open (observer should re-attach)

Summary

This is a well-scoped, clearly motivated fix. The code is thoroughly commented, the three approaches are the right ones for their respective problems, and test coverage is
comprehensive. The most complex part (exit transition options) is well-documented and handles edge cases.

The concurrent-mode ref mutation is a minor theoretical concern but consistent with existing MUI patterns. Ready to approve with no blocking issues.

@mj12albert
Copy link
Copy Markdown
Member Author

Potential concern with concurrent mode: Ref mutations during render are technically unsafe in concurrent React

Oops fixed the refs

@mj12albert mj12albert added v7.x needs cherry-pick The PR should be cherry-picked to master after merge. labels Apr 21, 2026
@mj12albert mj12albert enabled auto-merge (squash) April 21, 2026 10:41
@mj12albert mj12albert merged commit bbd7660 into mui:master Apr 21, 2026
21 checks passed
@github-actions
Copy link
Copy Markdown

Cherry-pick PRs will be created targeting branches: v7.x

@mj12albert mj12albert deleted the autocomplete/popper-rendering-improvements-5of5 branch April 21, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs cherry-pick The PR should be cherry-picked to master after merge. scope: autocomplete Changes related to the autocomplete. This includes ComboBox. type: bug It doesn't behave as expected. v7.x

Projects

None yet

2 participants