Repository: mui/material-ui (TypeScript)
Date: 2024-02-28 04:56:53+00:00
Task type: Bug Fix

Issue:
[material-ui][Autocomplete] The options list is added to the DOM even when there are no options, causing style problems
### Search keywords

Autocomplete, freeSolo, empty-options-list

### Latest version

- [X] I have tested the latest version

### Steps to reproduce

Link to live example: (required)
https://codesandbox.io/p/sandbox/practical-meadow-yq7hs5

Steps:
1. Click on freeSolo Input Box
2. Notice the red border below to the input field which actually is a paper container for options list.


### Current behavior
When there are no options for freeSolo Autocomplete input box, The list dropdown is being render on the UI. This becomes issue when I use some custom styles for that root paper element. Please check below image.
![Screenshot 2024-01-29 at 8 17 38 PM](https://github.com/mui/material-ui/assets/54258397/05029b68-25cd-4adf-9ee0-20ffca4197f2)


### Expected behavior
When there are no options available for freeSolo input, We shouldn't render/mount the options list.

### Context
We are maintaining our own theme where we applied some custom styles to this options list in Autocomplete component. So when there are no options available for Autocomplete freeSolo input, the empty options list is getting rendered unnecessarily with the custom styles. So when user is trying to input something by clicking on input field and he shows these styles without making any sense. 

### Your environment

<details>
  <summary><code>npx @mui/envinfo</code></summary>

```
  Don't forget to mention which browser you used.
  Output from `npx @mui/envinfo` goes here.
```
</details>

**System**:
    OS: macOS 14.1.1

**Binaries**:
    Node: 14.21.1 - ~/.nvm/versions/node/v14.21.1/bin/node
    npm: 6.14.17 - ~/.nvm/versions/node/v14.21.1/bin/npm
    pnpm: Not Found

**Browsers**:
    Chrome: 121.0.6167.85
    Edge: 121.0.2277.83
    Safari: 17.1

**npmPackages**:
    @emotion/react: ^11.10.4 => 11.11.1 
    @emotion/styled: ^11.10.4 => 11.11.0 
    @mui/base:  5.0.0-beta.17 
    @mui/core-downloads-tracker:  5.14.11 
    @mui/icons-material: ^5.11.11 => 5.14.11 
    @mui/material: ^5.10.7 => 5.14.11 
    @mui/private-theming:  5.14.11 
    @mui/styled-engine:  5.14.11 
    @mui/system:  5.14.11 
    @mui/types:  7.2.4 
    @mui/utils:  5.14.11 
    @mui/x-data-grid: ^5.17.11 => 5.17.26 
    @mui/x-date-pickers: ^5.0.19 => 5.0.20 
    @types/react: ^18.0.21 => 18.2.22 
    react: ^18.2.0 => 18.2.0 
    react-dom: ^18.2.0 => 18.2.0 
    typescript: ^4.8.4 => 4.9.5 


Issue discussion:
I would like to work on this issue. Please let me know.
Seems like a bug. The Popper appears in the DOM even when there are no options in free solo mode. This might have been done to handle the loading state with the `loading` prop when options are still being fetched and also when we want to show "no options" when it is not in free solo mode.

@DSK9012 You can work on it. Thanks.
Can i pick this up?
@rakeshmusturi Feel free to!

Solution patch:
diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.js b/packages/mui-material/src/Autocomplete/Autocomplete.js
--- a/packages/mui-material/src/Autocomplete/Autocomplete.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.js
@@ -582,6 +582,76 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) {
   const popperSlotProps = slotProps.popper ?? componentsProps.popper;
   const popupIndicatorSlotProps = slotProps.popupIndicator ?? componentsProps.popupIndicator;
 
+  const renderAutocompletePopperChildren = (children) => (
+    <AutocompletePopper
+      as={PopperComponent}
+      disablePortal={disablePortal}
+      style={{ width: anchorEl ? anchorEl.clientWidth : null }}
+      ownerState={ownerState}
+      role="presentation"
+      anchorEl={anchorEl}
+      open={popupOpen}
+      {...popperSlotProps}
+      className={clsx(classes.popper, popperSlotProps?.className)}
+    >
+      <AutocompletePaper
+        ownerState={ownerState}
+        as={PaperComponent}
+        {...paperSlotProps}
+        className={clsx(classes.paper, paperSlotProps?.className)}
+      >
+        {children}
+      </AutocompletePaper>
+    </AutocompletePopper>
+  );
+
+  let autocompletePopper = null;
+  if (!loading && groupedOptions.length > 0) {
+    autocompletePopper = renderAutocompletePopperChildren(
+      <AutocompleteListbox
+        as={ListboxComponent}
+        className={classes.listbox}
+        ownerState={ownerState}
+        {...otherListboxProps}
+        {...ListboxProps}
+        ref={combinedListboxRef}
+      >
+        {groupedOptions.map((option, index) => {
+          if (groupBy) {
+            return renderGroup({
+              key: option.key,
+              group: option.group,
+              children: option.options.map((option2, index2) =>
+                renderListOption(option2, option.index + index2),
+              ),
+            });
+          }
+          return renderListOption(option, index);
+        })}
+      </AutocompleteListbox>,
+    );
+  } else if (loading && groupedOptions.length === 0) {
+    autocompletePopper = renderAutocompletePopperChildren(
+      <AutocompleteLoading className={classes.loading} ownerState={ownerState}>
+        {loadingText}
+      </AutocompleteLoading>,
+    );
+  } else if (groupedOptions.length === 0 && !freeSolo && !loading) {
+    autocompletePopper = renderAutocompletePopperChildren(
+      <AutocompleteNoOptions
+        className={classes.noOptions}
+        ownerState={ownerState}
+        role="presentation"
+        onMouseDown={(event) => {
+          // Prevent input blur when interacting with the "no options" content
+          event.preventDefault();
+        }}
+      >
+        {noOptionsText}
+      </AutocompleteNoOptions>,
+    );
+  }
+
   return (
     <React.Fragment>
       <AutocompleteRoot
@@ -646,70 +716,7 @@ const Autocomplete = React.forwardRef(function Autocomplete(inProps, ref) {
           },
         })}
       </AutocompleteRoot>
-      {anchorEl ? (
-        <AutocompletePopper
-          as={PopperComponent}
-          disablePortal={disablePortal}
-          style={{
-            width: anchorEl ? anchorEl.clientWidth : null,
-          }}
-          ownerState={ownerState}
-          role="presentation"
-          anchorEl={anchorEl}
-          open={popupOpen}
-          {...popperSlotProps}
-          className={clsx(classes.popper, popperSlotProps?.className)}
-        >
-          <AutocompletePaper
-            ownerState={ownerState}
-            as={PaperComponent}
-            {...paperSlotProps}
-            className={clsx(classes.paper, paperSlotProps?.className)}
-          >
-            {loading && groupedOptions.length === 0 ? (
-              <AutocompleteLoading className={classes.loading} ownerState={ownerState}>
-                {loadingText}
-              </AutocompleteLoading>
-            ) : null}
-            {groupedOptions.length === 0 && !freeSolo && !loading ? (
-              <AutocompleteNoOptions
-                className={classes.noOptions}
-                ownerState={ownerState}
-                role="presentation"
-                onMouseDown={(event) => {
-                  // Prevent input blur when interacting with the "no options" content
-                  event.preventDefault();
-                }}
-              >
-                {noOptionsText}
-              </AutocompleteNoOptions>
-            ) : null}
-            {groupedOptions.length > 0 ? (
-              <AutocompleteListbox
-                as={ListboxComponent}
-                className={classes.listbox}
-                ownerState={ownerState}
-                {...otherListboxProps}
-                {...ListboxProps}
-                ref={combinedListboxRef}
-              >
-                {groupedOptions.map((option, index) => {
-                  if (groupBy) {
-                    return renderGroup({
-                      key: option.key,
-                      group: option.group,
-                      children: option.options.map((option2, index2) =>
-                        renderListOption(option2, option.index + index2),
-                      ),
-                    });
-                  }
-                  return renderListOption(option, index);
-                })}
-              </AutocompleteListbox>
-            ) : null}
-          </AutocompletePaper>
-        </AutocompletePopper>
-      ) : null}
+      {anchorEl ? autocompletePopper : null}
     </React.Fragment>
   );
 });


Test patch:
diff --git a/packages/mui-material/src/Autocomplete/Autocomplete.test.js b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
--- a/packages/mui-material/src/Autocomplete/Autocomplete.test.js
+++ b/packages/mui-material/src/Autocomplete/Autocomplete.test.js
@@ -2336,6 +2336,22 @@ describe('<Autocomplete />', () => {
 
       expect(container.querySelector(`.${classes.endAdornment}`)).to.equal(null);
     });
+
+    it('should not render popper when there are no options', () => {
+      render(
+        <Autocomplete
+          open
+          freeSolo
+          options={[]}
+          renderInput={(params) => <TextField {...params} />}
+          slotProps={{
+            popper: { 'data-testid': 'popperRoot' },
+          }}
+        />,
+      );
+      const popper = screen.queryByTestId('popperRoot');
+      expect(popper).to.equal(null);
+    });
   });
 
   describe('prop: onChange', () => {