Closed VladimirMilenko closed 2 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.
@M1K3Yio I suppose, that the main issue is hover
state of item in list. Which is also slow in your example.
Duplicate of #2711
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;
@endbay It worked for me. Thanks!
const { onMouseMove, onMouseOver, ...newInnerProps } = props.innerProps
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
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?
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.
@shunfan solution (object destructuring) is cleaner to me
Can someone post a full code sample of the solution that @shunfan proposed?
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
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 !
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 ?
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.
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.
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.
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
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.
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
This did the trick. Thanks man
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:
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.
@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.
@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.
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 .
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 destructinginnerProps
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} />
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):
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
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.
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.
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} />
@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.
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}
/>
)
}
People coming from Material-UI, follow @LukasMueller187 solution. It's the most comprehensive one.
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>
);
}
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}
/>
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,
}
})
}}
/>
);
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})}
...
/>
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.
Hi @M1K3Yio , This might seems solving the problem . but when typing in search with no option matches , it would show the no options message
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;
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}
/>
);
}
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.
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
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!
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
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.
THIS WORKED PERFECTLY FOR ME, comment link
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!
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
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.
I think delegation might help. Instead of subscribing every option element to mouseMove and mouseOver events we can subscribe only the parent element.
Also optimizing the component for large lists Thanks @rafaelbiten https://github.com/JedWatson/react-select/pull/2806#issuecomment-744386432
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.
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!
@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.
Low performance on large sets of options
React-select slows down when you have a huge array of data. Mouse
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
React-select version: 2.1.0