JedWatson / react-select

The Select Component for React.js
https://react-select.com/
MIT License
27.62k stars 4.12k forks source link

React-select is slow when you have more than 1000 items #3128

Closed VladimirMilenko closed 2 years ago

VladimirMilenko commented 6 years ago

Low performance on large sets of options

React-select slows down when you have a huge array of data. Mouse

screenshot 2018-10-19 at 15 29 43 screenshot 2018-10-19 at 15 51 21

FPS drops so low on mouseover, that i can barely use it. In my real case, where i have 1010 items, which i need to show(and i can't load them as user types) - i cannot do anything at all.

You can find a simple example in codesandbox.

https://codesandbox.io/s/q8l6xnvz7w

[Violation] 'mouseover' handler took 346ms
[Violation] 'mouseover' handler took 184ms
[Violation] 'mouseover' handler took 197ms
[Violation] 'mouseover' handler took 172ms
[Violation] 'mouseover' handler took 161ms
[Violation] 'mouseover' handler took 150ms
[Violation] 'mouseover' handler took 167ms
[Violation] 'mouseover' handler took 172ms
[Violation] 'mouseover' handler took 156ms
[Violation] 'mouseover' handler took 166ms

React-select version: 2.1.0

M1K3Yio commented 6 years ago

Two things to look into that may help with your issues, we also had some issues regarding large lists.

filterOption={createFilter({ignoreAccents: false})}

Take a look at this reported bug https://github.com/JedWatson/react-select/issues/2850

There's two examples using React-Window, which greatly improves the performance.

https://codesandbox.io/s/lxv7omv65l

VladimirMilenko commented 6 years ago

@M1K3Yio I suppose, that the main issue is hover state of item in list. Which is also slow in your example.

compwright commented 6 years ago

Duplicate of #2711

endze1t commented 6 years ago

I have a quick work around fix: Pass your own MenuList component, iterate through the childs and remove this props below. It stops lagging then. But you need to add styles for hover via css then.

delete key.props.innerProps.onMouseMove; delete key.props.innerProps.onMouseOver;

shunfan commented 5 years ago

@endbay It worked for me. Thanks!

const { onMouseMove, onMouseOver, ...newInnerProps } = props.innerProps
anarasimhan commented 5 years ago

I am running into the same issue. I have only about 300 items in the list. Looking at the code, removing the onMouseOver and onMouseMove will cause some functionality to not work.

Will this be fixed anytime soon?

Anand

malyushkin commented 5 years ago

I have a quick work around fix: Pass your own MenuList component, iterate through the childs and remove this props below. It stops lagging then. But you need to add styles for hover via css then.

delete key.props.innerProps.onMouseMove; delete key.props.innerProps.onMouseOver;

Could you show the code?

endze1t commented 5 years ago
        const MenuList = function MenuList(props) {
            const children = props.children;

            if (!children.length) {
                return (<div className="myClassListName">{children}</div>);
            }

            return (
                    <div className="myClassListName">
                        {children.length && children.map((key, i) => {
                            delete key.props.innerProps.onMouseMove; //FIX LAG!!
                            delete key.props.innerProps.onMouseOver;  //FIX LAG!!

                            return (
                                <div className="myClassItemName" key={i}>{key}</div>
                            );
                        })}
                    </div>
            );
        };
    <Select
           components={{
                            MenuList
                        }}
         />

Probably not the best approach. But it's working for me. If anyone has suggestions for improvement, I would be grateful.

sebastiendan commented 5 years ago

@shunfan solution (object destructuring) is cleaner to me

CWSites commented 5 years ago

Can someone post a full code sample of the solution that @shunfan proposed?

kotvasili commented 5 years ago
const Option = ({ children, ...props }) => {
  const { onMouseMove, onMouseOver, ...rest } = props.innerProps;
  const newProps = Object.assign(props, { innerProps: rest });
  return (
    <components.Option
      {...newProps}
    >
      {children}
    </components.Option>
  );
};

Example of @shunfan solution

willbee28 commented 5 years ago

It is insane that ignoreAccents' default is set to true!

Setting this to false, in my case where I'm pairing react-select with react-virtualized, resulted in huge perforance boosts, with very little lag if any. Been looking for this solution for a long time. Thanks @endbay !

VincentLanglet commented 5 years ago
const { onMouseMove, onMouseOver, ...newInnerProps } = props.innerProps

Seems to only be a workaround. Is it planned to fix this problem ? Are there any ideas to start with ?

juanpicado commented 5 years ago

The performance penalty for filter each option in a quite large set of data is insanely slow on force reconciliation a new react-select , as https://github.com/JedWatson/react-select/issues/3128#issuecomment-431397942 suggested must be disabled, I even would recommend the author disable it by default.

 <Select filterOption={null}

In my case, I am dealing with a set of 2000 items and I do not need to filter, so I disable the whole thing, the speed bost quite a lot, check the time just the filter method takes.

Screenshot 2019-03-12 at 16 35 38

Furthermore, following the recommendation of https://github.com/JedWatson/react-select/issues/2850 I'm using react-window with a custom MenuList for only large datasets. Here the implementation in Typescript.

import React, { Component, ReactNode } from "react";
import { FixedSizeList as List } from "react-window";
// <= IOption is my custom interface for each option
import { IOption } from "./FormSelect";
import { MenuListComponentProps } from "react-select/lib/components/Menu";
import { ValueType } from "react-select/lib/types";

const height: number = 35;
class MenuList extends Component<MenuListComponentProps<IOption>> {
    public render(): React.ReactElement<HTMLElement> {
        const { options, children, maxHeight, getValue } = this.props;
        // @ts-ignore
        const [value]: ValueType<IOption> = getValue();
        const initialOffset: number = options.indexOf(value) * height;
        const childrenOptions: React.ReactChild[] = React.Children.toArray(children);

        return (
            <List
                height={maxHeight}
                itemCount={childrenOptions.length}
                itemSize={height}
                initialScrollOffset={initialOffset}
            >
                {this.rendersomething(children)}
            </List>
        );
    }

    public rendersomething = (children: ReactNode): any => {
        return (props: any): React.ReactChild => {
            const { index, style } = props;

            return <div style={style}>{children[index]}</div>;
        };
    }
}

export default MenuList;

I hope that helps, this is still a workaround, but it works for me pretty well.

VincentLanglet commented 5 years ago

The performance penalty for filter each option in a quite large set of data is insanely slow on force reconciliation a new react-select , as #3128 (comment) suggested must be disabled, I even would recommend the author disable it by default.

 <Select filterOption={false}

In my case I am dealing with set of 2000 items and I do not need to filter, so I disable the whole thing, the speed bost quite a lot, check the time just the filter method takes.

There is no interest of autocomplete without filtering the options. It's just a simple select otherwise.

johnnyreilly commented 5 years ago

If it's the typing lag that you're bumping on (I was) then it's actually the filterOption={createFilter({ignoreAccents: false})} suggestion by @M1K3Yio that's the gem.

Using this massively reduces the lag when you're typing into the select. I've got a sandbox here that illustrates: https://codesandbox.io/s/zn70lqp31m?fontsize=14

And I've blogged it too

SeanRoberts commented 5 years ago

Removing the mouse events from Option and providing your own hover styling via CSS improves performance considerably, but it means the isFocused prop doesn't get updated on hover, which results in multiple elements being highlighted when you hover (assuming you have a highlight style set for isFocused

Even a list with relatively few items is very slow with the mouse events enabled, I have a list with <100 items and running my mouse up and down the list is super laggy. I'm not sure what the solution could be here, removing styling for isFocused makes the component impossible to use with the keyboard. Another option might be to make use of React.memo and possibly useCallback or other memoization tricks in order to prevent so many re-renders. In theory when focus changes only two options should need to be updated: the option that is going from not focused to focused, and the option that's going from focused to not focused. I tried the super naive approach of just:

export default React.memo(Option, (prevProps: any, nextProps: any) => {
  return prevProps.isFocused === nextProps.isFocused;
});

But there are definitely more props than just those changing as subsequent focus changes after the first one don't cause any elements to re-render.

mreliopez commented 5 years ago

If it's the typing lag that you're bumping on (I was) then it's actually the filterOption={createFilter({ignoreAccents: false})} suggestion by @M1K3Yio that's the gem.

Using this massively reduces the lag when you're typing into the select. I've got a sandbox here that illustrates: https://codesandbox.io/s/zn70lqp31m?fontsize=14

And I've blogged it too

This did the trick. Thanks man

Haosik commented 5 years ago

Two more hints that may help someone (I hope), that helped me after struggling with even react-modal-solution being a bit laggy. Deadly lags appeared while hovering and "mouseovering" the dropdown elements, with even "poor" 250 options. So hints that may help:

nadzimo commented 5 years ago

Facing slowness too for only 250 items making the widget a no-go for us in production. I'm on version 3.0.4. I tried destructing innerProps to remove the hover props as @endbay and @kotvasili suggested but it still didn't work for me.

willbee28 commented 5 years ago

@nadzimo The ignore accents help a ton, idk if you've done that already, but I have about that many items if not more in my dropdown. Also using the custom made MenuList item with react-virtualized. I didn't use the specific one in this thread, but another shorter one in another github forum/question. Let me know if you need help finding it.

nadzimo commented 5 years ago

@willbee28, I wasn't familiar with react-virtualized and just checked it out – looks slick. Will give it a go. I tried the ignore accents but no improvement. I think my issue is not related to search, it looks like it's related to the hover events.

willbee28 commented 5 years ago

I recommend using it with the MenuList component, utilizing the List component from react-virtualized.

On Wed, Jul 31, 2019, 4:50 PM nadzimo notifications@github.com wrote:

@willbee28 https://github.com/willbee28, I wasn't familiar with react-virtualized and just checked it out – looks slick. Will give it a go.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/JedWatson/react-select/issues/3128?email_source=notifications&email_token=AGI42AODEKOSVLTCIX2EHI3QCH3JRA5CNFSM4F6BPYAKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD3IQR4Q#issuecomment-517015794, or mute the thread https://github.com/notifications/unsubscribe-auth/AGI42APFLDEWOZI5LTMHBDDQCH3JRANCNFSM4F6BPYAA .

gydotitulaer commented 5 years ago

Facing slowness too for only 250 items making the widget a no-go for us in production. I'm on version 3.0.4. I tried destructing innerProps to remove the hover props as @endbay and @kotvasili suggested but it still didn't work for me.

I've used the ignore accents when calling the ReactSelect, that worked for me, give it a try..!

<ReactSelect className="react-select" styles={customStyles} ignoreAccents={false} {...props} />

keepforever commented 5 years ago

I was able to get a satisfactory user experience by just making it an AsyncSelect.

https://react-select.com/async

Here's my implementation of AsyncSelect (after):

https://github.com/keepforever/deck-app-web/tree/05-add-alt-card/src/comps/Deck/AsyncBoilerAutoComplete

Note, it looks super complex because I'm using a ton of boiler from the Material-Ui integreation. But, I didn't have to change much between the props passed to Select and AsyncSelect.

Here's Select (poor performance) before.

https://github.com/keepforever/deck-app-web/tree/05-add-alt-card/src/comps/Deck/BoilerAutoComplete

LukasMueller187 commented 5 years ago

Summarized and working for me:

import React from 'react';
import Select, {createFilter, components} from 'react-select';

class YourComponent extends React.Component{
    constructor(props){
    super(props);
    }
    render(){
    return <>
                    <Select options={yourOptions}                            
                        filterOption={createFilter({ignoreAccents: false})}
                        components={{Option: CustomOption}}/>
    </>;
    }
}

class CustomOption extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const {innerProps, isFocused, ...otherProps} = this.props;
        const {onMouseMove, onMouseOver, ...otherInnerProps} = innerProps;
        const newProps = {innerProps: {...otherInnerProps}, ...otherProps};
        return (
            <components.Option {...newProps} className="your-option-css-class">{this.props.children}
            </components.Option>
        );
    }
}

and your css file:

.your-option-css-class:hover {
     background-color: green;
}

Hope somebody can make use of this too. I'd still prefer a fix.

starrybleu commented 5 years ago

Can I apply filterOption={createFilter({ignoreAccents: false})} to <AsyncCreatable/> ?

Actually, I did that but it won't work now.

If I put a filtering logic into loadOptions function, then I correctly filtered.

gydotitulaer commented 5 years ago

Can I apply filterOption={createFilter({ignoreAccents: false})} to <AsyncCreatable/> ?

Actually, I did that but it won't work now.

If I put a filtering logic into loadOptions function, then I correctly filtered.

For me filterOption didn't work, so i added ignoreAccents={false} on the <Select /> component.

Soo for example:

import Select from 'react-select';

<Select options={yourOptions} ignoreAccents={false} />
starrybleu commented 5 years ago

@gydotitulaer Thanks for your response. I intended to focus on async select components such as <AsyncSelect/>, <AsyncCreatable/> which should be along with loadOptions prop.

However, the problem I mentioned previously came out because I made a mistake with importing wrong function createFilter while I tried some sort of ways.

After I imported correct createFilter function from react-select, then it worked.

TLDR;

The following code worked as expected.

import {AsyncCreatable, createFilter} from 'react-select'

loadOptionsAsyncWithoutFiltering = () => {
    // ...
    return Promise.resolve([...someArrays])
}

// ...
render() {
    return (
    // ...
    <AsyncCreatable {...otherProps} filterOption={createFilter({ignoreAccents: false})}
        loadOptions={this.loadOptionsAsyncWithoutFiltering}
    />
    )
}
rxhl commented 5 years ago

People coming from Material-UI, follow @LukasMueller187 solution. It's the most comprehensive one.

parkerself22 commented 5 years ago

Had the same issue as everyone else in this thread, in our use case we really never needed to render the entire list if there was no search text entered, so I went with the below approach of just rendering the first 50 elements. Not sure if the useMemo is necessary tbh, someone more familiar with react-select's internals feel free to chime in on that. Either way, this solved the lag problems we were running into.

// The returned MenuList is the default react-select component
function MenuListOverride(props: MenuListComponentProps<OptionType>) {
    const children = React.useMemo(() => React.Children.toArray(props.children), [props.children]);
    return (
          <MenuList {...props}>
              {children.length > 50 ? children.slice(0, 50) : props.children}
          </MenuList>
     );
}
diegomolinavera commented 4 years ago

I have almost the same issue, but I'm displaying more than 3.000 elements. What i did was the to use React useMemo and customize the MenuList and the Option component. At start the display list takes 3.6s (3872 items) and later everything takes 2.5s. It's not a huge improvement but it's something. I think you could gain some ms using a normal for loop (well done) instead of a map function and display div instead of the <components.Option /> but that depends of the requirements

import Select, { components } from 'react-select';

function MenuList(props) {
  const makeOptions = useMemo(
    () => props.children.map(child => {
      const { onMouseMove, onMouseOver, ...rest } = child.props.innerProps;
      const tempProps = { ...child.props, ...{ innerProps: rest } };

      return <components.Option {...tempProps} key={rest.id} />;
    }),
    [props.options]
  );

  return <components.MenuList {...props}>{makeOptions}</components.MenuList>;
}

<Select
  components={MenuList}
  options={opt}
  value={value}
/>
albertjan commented 4 years ago

We ended up using this:

import React from "react";

import { flavourOptions } from "./docs/data";
import Select, { components } from "react-select";
import { onlyUpdateForKeys, compose, mapProps } from "recompose";
import omit from "lodash/fp/omit";

const omitProps = (...keys) => mapProps(omit(keys));
const FastOption = compose(
  onlyUpdateForKeys(["isDisabled", "isSelected"]),
  omitProps("isFocused")
)(components.Option);

export default () => (
  <Select
    components={{
      Option: FastOption
    }}
    defaultValue={flavourOptions[2]}
    label="Single select"
    options={flavourOptions}
    styles={{
      option: (style, state) => ({
        ...style,
        "&:hover": {
          backgroundColor: state.theme.colors.primary25,
          color: state.theme.colors.neutral90,
        }
      })
    }}
  />
);
mairisb commented 4 years ago

Solution for Typescript users:

import * as React from "react";
import { MenuListComponentProps, OptionTypeBase } from "react-select";
import { FixedSizeList } from "react-window";

...

export default class OptimizedMenuList extends React.Component<MenuListComponentProps<OptionTypeBase>> {
    private readonly height = 40;

    public render() {
        const { options, children, maxHeight, getValue } = this.props;
        const selectedValues = getValue();
        const initialOffset = selectedValues && selectedValues[0] ? options.indexOf(selectedValues[0]) * this.height : 0;

        return(
            <FixedSizeList
                height={maxHeight}
                itemCount={children!["length"]}
                itemSize={this.height}
                initialScrollOffset={initialOffset}
                width={""} // 100% width
            >
                {({ index, style }) => <div className="option-wrapper" style={style}>{children![index]}</div>}
            </FixedSizeList>
        );
    }
}
import * as React from "react";
import { components, OptionTypeBase, OptionProps } from "react-select";

...

export default class OptimizedOption extends React.Component<OptionProps<OptionTypeBase>> {

    public render() {
        delete this.props.innerProps.onMouseMove;
        delete this.props.innerProps.onMouseOver;

        return (
            <components.Option
                {...this.props}
            >
                {this.props.children}
            </components.Option>
        );
    }
}
import * as React from "react";
import AutoComplete, { createFilter } from "react-select";
import OptimizedMenuList from "./OptimizedMenuList";
import OptimizedOption from "./OptimizedOption";

...

<AutoComplete
    ...
    components={{MenuList: OptimizedMenuList, Option: OptimizedOption}}
    filterOption={createFilter({ignoreAccents: false})}
    ...
/>
alex-sh-li commented 4 years ago

Two things to look into that may help with your issues, we also had some issues regarding large lists.

filterOption={createFilter({ignoreAccents: false})}

Take a look at this reported bug #2850

There's two examples using React-Window, which greatly improves the performance.

https://codesandbox.io/s/lxv7omv65l

Hi @M1K3Yio , This might seems solving the problem . but when typing in search with no option matches , it would show the no options message

alex-sh-li commented 4 years ago

Two things to look into that may help with your issues, we also had some issues regarding large lists. filterOption={createFilter({ignoreAccents: false})} Take a look at this reported bug #2850 There's two examples using React-Window, which greatly improves the performance. https://codesandbox.io/s/lxv7omv65l

Hi @M1K3Yio , This might seems solving the problem . but when typing in search with no option matches , it would show the no options message

I replaced the NoOptionsComponent with the custom no options component as there is something wrong in react-select / react-window which lead to the no options message component cannot be shown

import React from 'react';
import { components } from 'react-select';

const NoOptionsMessage = props => {

    const { children, ...newProps } = props;

    return (
        <components.NoOptionsMessage {...newProps} />
    );
};

export default NoOptionsMessage;
diegomolinavera commented 4 years ago

I'd like to update my code. This works like a charm for me 😎 . I was able to work with more than 3000 items.

First of all, I created a new Menu list and I also made use of useMemo hook. I removed useless - for me and the client - props

function MenuList(props) {
  const makeOptions = useMemo(
    () =>
      props.children.map(child => {
        const {
          // this will be removed
          selectOption,
          isFocused,
          hasValue,
          setValue,
          innerRef,
          isRtl,
          //
          innerProps,
          ...restChild
        } = child.props;
        const { onMouseMove, onMouseOver, ...rest } = innerProps;
        const tempProps = { ...restChild, ...{ innerProps: rest } };

        return <components.Option {...tempProps} key={rest.id} />;
      }),
    [props.options]
  );

  return <components.MenuList {...props}>{makeOptions}</components.MenuList>;
}

Then I made use of the CreatetableSelect component which helped me to add items one by one ( or in my case 160 by 160) as soon as I reached the bottom of the list. This seems sort of virtualized list. I noticed that everytime I closed the menu and the list was already huge, when opened it again was slowly; to solve this I clear the list of items, starting everything again from an small amount of items.

function filterColors(data) {
  // You can replace includes for another functions... I was just a little bit lazy 😄 
  return inputValue => data.filter(i => i.label.toLowerCase().includes(inputValue.toLowerCase()));
}

const Component = props => {
const [state, setState] = useState([]);

const AMOUNT_ITEMS = 160;
const filterFnc = filterColors(props.options);

useEffect(() => {
  const { options } = props;
  // Load the first amount of items
  const optSlice = options.slice(0, AMOUNT_ITEMS); 
  onCreateOptionHandler(optSlice);
}, []);

// add new items
function onCreateOptionHandler(tmpOptions) {
    setState([...state, ...tmpOptions]);
}
// remove the added items and left the very first amount of them
function onMenuCloseHandler() {
    setState(options.slice(0, AMOUNT_ITEMS));
}

// We filter the options (the added items and not)
function loadOptionsHandler(inputValue, callback) {
    const time = setTimeout(() => {
      callback(filterFnc(inputValue));
      clearTimeout(time);
    }, 1600);
  }

// add items everytime we reach the end of the menu list
function onMenuScrollToBottomHandler() {
    const { options } = props;
    const tmpOptionSize = state.length;

    if (options.length !== tmpOptionSize) {
        const optSlice = options.slice(tmpOptionSize, tmpOptionSize + AMOUNT_ITEMS);
        onCreateOptionHandler(optSlice);
     }
  }

return (
  <CreatableSelect
     onMenuScrollToBottom={onMenuScrollToBottomHandler}
     onCreateOption={onCreateOptionHandler}
     onMenuClose={onMenuCloseHandler}
     loadOptions={loadOptionsHandler}
     formatCreateLabel={inputValue => ''} // needed to remove the 'Create new ...' label
     options={state}
   />
);
}
rafaelbiten commented 4 years ago

Based on the responses above, I came up with this:

import { MenuListComponentProps } from 'react-select/lib/components/Menu'
import { FixedSizeList } from 'react-window'
import { OptionProps } from 'react-select/lib/components/Option'
import { components } from 'react-select'
import React from 'react'

export const optimizeSelect = {
  components: {
    MenuList: OptimizedMenuList,
    Option: OptimizedOption,
  },
}

function OptimizedMenuList(props: MenuListComponentProps<SelectOption>) {
  const { options, children, maxHeight, getValue } = props
  if (!children || !Array.isArray(children)) return null

  const height = 35
  const selectedValues = getValue() as SelectOption[]
  const initialOffset = selectedValues[0] ? options.indexOf(selectedValues[0]) * height : 0

  return (
    <FixedSizeList
      width={''}
      itemSize={height}
      height={maxHeight}
      itemCount={children.length}
      initialScrollOffset={initialOffset}
    >
      {({ index, style }) => (
        <div className="option-wrapper" style={style}>
          {children[index]}
        </div>
      )}
    </FixedSizeList>
  )
}

function OptimizedOption(props: OptionProps<SelectOption>) {
  delete props.innerProps.onMouseMove
  delete props.innerProps.onMouseOver
  return <components.Option {...props}>{props.children}</components.Option>
}

// SelectOption is specific to this example
// and may not work with other projects
type SelectOption = {
  value: string
  label: string
  [key: string]: string
}

Then, whenever I have to optimize a <Select /> I pass a couple of extra props, ie.:

<Select
  {...theOtherProps}
  filterOption={createFilter({ ignoreAccents: false })}
  components={optimizeSelect.components}
/>

Given the required dependencies are installed, others using typescript should be able to create a file for the optimizeSelect and use it as in the example above.

torian257x commented 4 years ago

hm, this component was praised as "the best solution" and it cannot handle a couple thousand entries?

after I click, it takes seconds to render the drop down and in the console I have

Warning: React instrumentation encountered an error: RangeError: Maximum call stack size exceeded

a bit disappointed, honestly.

neither ignoreAccents={false} nor filterOption={createFilter({ignoreAccents: false})}

seems to help we have 9k entries

my whole browser freezes and stutters, it's absolutely unusable

torian257x commented 4 years ago

looks like https://github.com/JedWatson/react-select/issues/3128#issuecomment-576809096 works

damianmr commented 4 years ago

I'd remove Jossnaz's comment above ☝️ . It's just not useful at all and this person clearly doesn't understand how hard it is to build a component like this. Try using a native browser's dropdown and tell me how far you get in functionality!

Just wanted to say that I found this issue while trying to debug some performance problems I was having and found all the discussion very enriching. I'm using react-virtualized as well and my selects start to lag around 500 elements.

I only wanted to ask two things: 1- Is there any loss in functionality if I remove the mouse handlers? 2- Would it be useful to have a wiki entry explaining this? (edit: I would volunteer for this if that's the case) Like many others, my use case is to provide search in a large dataset, so, knowing this in advance (by referencing this issue in the docs) would be very helpful to plan ahead.

Thanks for the work!

torian257x commented 4 years ago

LOL @damianmr yes, go and sushh everyone who is disappointed to have to spend time figuring that one out, then proceed to point out they should write a wiki entry 😊

I recommend you read my comment above ☝️. It has the only solution that worked for me. The other solutions as pointed out in my other comment, didn't work for me. I'm sure it's helpful to some, maybe even you. Good luck

Mrkezii commented 4 years ago

Two things to look into that may help with your issues, we also had some issues regarding large lists.

filterOption={createFilter({ignoreAccents: false})}

Take a look at this reported bug #2850

There's two examples using React-Window, which greatly improves the performance.

https://codesandbox.io/s/lxv7omv65l

THIS WORKED PERFECTLY FOR ME, comment link

Methuselah96 commented 3 years ago

I've started a fork of react-select. Feel free to resubmit this issue on the fork and/or submit a PR to resolve this issue on the fork and we can get it merged and released.

EDIT: :tada: I've archived the fork now that we've got some momentum in this repo and Jed is involved again. Sorry for the disturbance!

dtaub commented 3 years ago

Worth noting that removing the mouse event handlers breaks keyboard navigation with the arrow keys. The whole reason focus is handled in javascript rather than css in the first place is to be able to navigate with both the mouse and keyboard like with a native select element.

It's a shame because removing those event handlers and applying the hover styles in css performs much much faster, but it's at the expense of breaking keyboard functionality.

I was able to virtualize the MenuList with react-virtuoso and it improves things a decent amount but it still seems to be bottlenecked by having to make all the React.createElement calls in renderMenu https://github.com/JedWatson/react-select/blob/master/packages/react-select/src/Select.js#L1681 every time focus state changes. I don't think there's any exposed way to override that (well... short of extending the Select class but 😬, edit: this is actually not possible because of HOCs, nor should it be really).

So I think the way to fix the hover lag while keeping keyboard functionality would be to

  1. Figure out how to not make the React.createElement calls for options that aren't actually being rendered. The virtualization solutions in this thread prevent rendering to the real DOM, which helps, but those virtual DOM calls still kill performance at a certain point.
  2. Memoize the options so on focus change only two options render: the one that lost focus and the one that gained focus. (Currently it renders all of them which is a big waste.)

It'll probably be a while before I come back to this to try to figure out implementing those so I figured I'd leave my thoughts on it here in case anyone wants to try to figure it out.

Lehakos commented 3 years ago

I think delegation might help. Instead of subscribing every option element to mouseMove and mouseOver events we can subscribe only the parent element.

renderlife commented 3 years ago

Also optimizing the component for large lists Thanks @rafaelbiten https://github.com/JedWatson/react-select/pull/2806#issuecomment-744386432

ebonow commented 3 years ago

Greetings,

I think these are important points and appreciate the conversation that taking place here. I have closed https://github.com/JedWatson/react-select/issues/2850 to continue the discussion here. There were several solutions and workarounds presented there, so feel free to browse through that thread should anyone wish to examine any implementations that might help users render more items in a more performant manner.

LukasMueller187 commented 3 years ago

I agree that this conversation here is helpful for a lot of users that try to use lots of items. Nevertheless, my opinion is that this issue should get fixed by the maintainers due to the dozens of different workarounds depending on the usecase. It is obvious that this (by the way awesome) react addon should be able to handle lots of items. Without trying several workarounds that are not straightforward. Thanks in advance!

ebonow commented 3 years ago

@LukasMueller187 agreed. Sharing these "work arounds" and solutions in the interim, but there is momentum and Jed has been communicative so it's very promising to see where things are headed.