mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.82k stars 32.26k forks source link

Select with more than 100 items in Modal appears on the left before repositioning #33743

Closed AngelGenchev95 closed 2 years ago

AngelGenchev95 commented 2 years ago

Duplicates

Latest version

Current behavior 😯

Currently when we have a MUI modal that contains a MUI select with more than 100 items, the first time you open that select, the items appear glued to the left of the screen, before repositioning to their intended spot.

Expected behavior 🤔

The items should immediately appear in their intended spot, relative to the input.

Steps to reproduce 🕹

Steps:

  1. Create a Modal
  2. Add a Select with more than 100 elements
  3. Click on the Select

Context 🔦

I'm trying to make a Select inside a modal that contains a list of countries.

Your environment 🌎

Working example: https://stackblitz.com/edit/react-ts-xefudx?file=App.tsx

siriwatknp commented 2 years ago

@AngelGenchev95 I tried your stackblitz but I could not see the issue. Can you check?

AngelGenchev95 commented 2 years ago

@siriwatknp Here's a video of the issue: https://user-images.githubusercontent.com/41321846/182336688-45bc69fb-0925-4231-b62f-37202a2b9c21.mov I'm using a macbook pro, Chrome

AlexKrupko commented 2 years ago

The same issue even without Modal component. Please see my screen record. 252 MenuItem components were used here.

https://user-images.githubusercontent.com/5411244/183246611-5247f0f3-82e5-47b0-a08e-1d7dbf873f7d.mov

React version: 18.2 MUI version: 5.9.3

AlexKrupko commented 2 years ago

Hi there,

The reason of this behavior is a big amount of options that are rendered simultaneously when select is opening. Even with 20 - 30 options this issue becomes noticeable. If we use 1000 options, the list is rendered at the left and only after ~3 seconds moves to the proper position.

I've found a several possible solutions/ideas:

  1. Use native select. This is the simplest solution (just add one prop), but unfortunately options list is out of common design and it looks a little bit ugly.

  2. Use virtualization for the options list. I tried to use Virtuoso for the options list but unfortunately I didn't find how to do it properly to don't break control with keyboard.

  3. Use Autocomplete instead of Select. It uses virtualization inside, options renders properly. But it provides a little bit another functionality then Select, also there are visual deferences.

  4. Render just one MenuItem component on select opening and then render all options after small delay. This solution I applied for my project. Initially I render just one MenuItem disabled component with progress bar inside. After 300ms delay all other options are rendered.

const FormPhoneNumber: FC<FormPhoneNumberProps> = ({}) =>
{
    const {data: countries = []} = useGetCountriesQuery();
    const [countryCode, setCountryCode] = useState<number | ''>('');
    const [isOpened, setIsOpened] = useState<boolean>(false);

    return (
        <TextField
            select
            SelectProps={{
                onOpen: () => setTimeout(() => setIsOpened(true), 300),
                onClose: () => setTimeout(() => setIsOpened(false), 300),
                renderValue: () =>
                {
                    const item = countries.find(({phoneCode}) => countryCode === phoneCode);
                    return item ? getOptionLabel(item) : '';
                },
            }}
            label="Country"
            value={countryCode}
            onChange={(event) => setCountryCode(Number(event.target.value))}
            fullWidth
        >
            {!isOpened && (
                <MenuItem
                    disabled
                    sx={{
                        opacity: '1 !important',
                        justifyContent: 'center',
                    }}
                    value={countryCode}
                >
                    <CircularProgress size={20} disableShrink />
                </MenuItem>
            )}

            {isOpened && countries.map((item) => (
                <MenuItem key={item.code} value={item.phoneCode}>
                    {getOptionLabel(item)}
                </Option>
            ))}
        </TextField>
    );
};

@AngelGenchev95, probably this solution will be also acceptable for you.

dgarcia-hu commented 2 years ago

Isn't this a duplicate of #33308? Also, this is happening to me with a Menu component.

michaldudak commented 2 years ago

Yes, this is a duplicate of #33308.