Closed VladimirMilenko closed 2 years ago
Can anyone give an example to fix this for grouped MenuList? When I use this https://github.com/JedWatson/react-select/issues/3128#issuecomment-576809096 example it overlaps groups on top of each other.
Trying this solution: https://codesandbox.io/s/lxv7omv65l with version 2.4.4 of react-select
and I cannot reference neither children[index]
nor const [value] = getValue()
Is the solution no longer valid in newer versions?
Is there any reason why virtualization isn’t built into react-select? I personally cannot think of a reason why it shouldn’t be.
Can anyone give an example to fix this for grouped MenuList? When I use this #3128 (comment) example it overlaps groups on top of each other.
I have been to this thread many time and I have implemented the solutions listed here with success a number of months ago. Recently I realized that this issue was back and the reason was that we started using the grouped. I am also interested in seeing how to use react-window in this situation.
When using a the grouped feature it changes what you get passed to an array of object, one object for each group that you have which break the example code for using using react-window to resolve the issue. I have tried various attempts to use react-window on each group but nothing has been very fruitful at this point.
Here is a code sand box that implements the above solutions that demonstrates it doesn't work with grouped menu lists. https://codesandbox.io/s/react-select-grouped-large-data-vycpx
Notes:
OptimizedMenuList - FixedSizeList - Child
gets called once per group
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.
This solution has improved the performance for common browsers like Chrome, Firefox and Safari significantly. For IE, there still seems to be some lagging for large dataset(9563 records). Have you had the same issue?
I'm also seeing performance degradations with IE11 specifically too (chrome/firefox is ok), haven't found a solution yet for IE11 yet...
I was bashing my head against the wall with this performance issue. Then, after watching user behavior, I've noticed that users don't scroll huge lists -- they're using exclusively search. So we've published a version that shows only a portion of options but searches through all of them:
import escapeRegExp from "lodash/escapeRegExp";
import React, { useState, useMemo } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import "./styles.css";
const MAX_DISPLAYED_OPTIONS = 500;
const options = [];
for (let i = 0; i < 10000; i = i + 1) {
options.push({ value: i, label: `Option ${i}` });
}
function App() {
const [inputValue, setInputValue] = useState("");
const filteredOptions = useMemo(() => {
if (!inputValue) {
return options;
}
const matchByStart = [];
const matchByInclusion = [];
const regByInclusion = new RegExp(escapeRegExp(inputValue), "i");
const regByStart = new RegExp(`^${escapeRegExp(inputValue)}`, "i");
for (const option of options) {
if (regByInclusion.test(option.label)) {
if (regByStart.test(option.label)) {
matchByStart.push(option);
} else {
matchByInclusion.push(option);
}
}
}
return [...matchByStart, ...matchByInclusion];
}, [inputValue]);
const slicedOptions = useMemo(
() => filteredOptions.slice(0, MAX_DISPLAYED_OPTIONS),
[filteredOptions]
);
return (
<Select
options={slicedOptions}
onInputChange={(value) => setInputValue(value)}
filterOption={() => true} // disable native filter
/>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Performance issues are gone and nobody noticed that some options are missing (since all options are searchable by typing). It's not a perfect solution but I hope my experience is helpful for someone
I was bashing my head against the wall with this performance issue. Then, after watching user behavior, I've noticed that users don't scroll huge lists -- they're using exclusively search. So we've published a version that shows only a portion of options but searches through all of them:
import escapeRegExp from "lodash/escapeRegExp"; import React, { useState, useMemo } from "react"; import ReactDOM from "react-dom"; import Select from "react-select"; import "./styles.css"; const MAX_DISPLAYED_OPTIONS = 500; const options = []; for (let i = 0; i < 10000; i = i + 1) { options.push({ value: i, label: `Option ${i}` }); } function App() { const [inputValue, setInputValue] = useState(""); const filteredOptions = useMemo(() => { if (!inputValue) { return options; } const matchByStart = []; const matchByInclusion = []; const regByInclusion = new RegExp(escapeRegExp(inputValue), "i"); const regByStart = new RegExp(`^${escapeRegExp(inputValue)}`, "i"); for (const option of options) { if (regByInclusion.test(option.label)) { if (regByStart.test(option.label)) { matchByStart.push(option); } else { matchByInclusion.push(option); } } } return [...matchByStart, ...matchByInclusion]; }, [inputValue]); const slicedOptions = useMemo( () => filteredOptions.slice(0, MAX_DISPLAYED_OPTIONS), [filteredOptions] ); return ( <Select options={slicedOptions} onInputChange={(value) => setInputValue(value)} filterOption={() => true} // disable native filter /> ); } ReactDOM.render(<App />, document.getElementById("root"));
Performance issues are gone and nobody noticed that some options are missing (since all options are searchable by typing). It's not a perfect solution but I hope my experience is helpful for someone
Thank you!
Hi everyone! I spent a few days solving the dropdown list freezing issue. I tried using the above described methods of creating MenuList
using react-window
. I also added a custom Option
component, removing props onMouseMove
and onMouseOver
. Seems this fixed my problem.
Example with react-window
codesandbox.
Update:
Example with react-virtuoso
codesandbox.
MenuList
import React from "react";
import { FixedSizeList as List } from "react-window";
const OPTION_HEIGHT = 40;
const ROWS = 6;
const MenuList = ({
options,
children,
getValue
}) => {
const [value] = getValue();
const initialOffset =
options.indexOf(value) !== -1
? Array.isArray(children) &&
children.length >= ROWS
? options.indexOf(value) >= ROWS
? options.indexOf(value) *
OPTION_HEIGHT -
OPTION_HEIGHT * 5
: 0
: 0
: 0;
return Array.isArray(children) ? (
<List
height={
children.length >= ROWS
? OPTION_HEIGHT * ROWS
: children.length * OPTION_HEIGHT
}
itemCount={children.length}
itemSize={OPTION_HEIGHT}
initialScrollOffset={initialOffset}
>
{({ style, index }) => {
return (
<div style={style}>
{children[index]}
</div>
);
}}
</List>
) : (
<div>{children}</div>
);
};
export default MenuList;
Option
import React from "react";
import cx from "classnames";
const Option = ({
children,
isSelected,
innerProps
}) => (
<div
className={cx("react-select__option", {
"react-select__option_selected": isSelected
})}
id={innerProps.id}
tabIndex={innerProps.tabIndex}
onClick={innerProps.onClick}
>
{children}
</div>
);
export default Option;
ReactSelect
import React from "react";
import Select from "react-select";
import MenuList from "./MenuList";
import Option from "./Option";
const ReactSelect = ({
options,
value,
onChange,
placeholder
}) => {
return (
<Select
options={options}
value={value && [value]}
onChange={onChange}
classNamePrefix="react-select"
placeholder={placeholder}
components={{
MenuList,
Option
}}
/>
);
};
export default ReactSelect;
We've tried using the react-window
solution suggested above, but that solution requires that you know the rendered height of each option. When options in a select are from user-generated content and the menu may be virtually any width, we have to calculate that on the fly every time.
Anyone know a virtualization lib that can be used that doesn't require you to know the exact height as rendered in the browser of every item in the list?
@chadlavi-casebook I used react-virtuoso. That works pretty nicely for varying height options.
@dtaub thanks, that suggestion looks great. Bonus that it's already written in typescript as well.
Using custom menu list here with react-virtualized. Can't figure out the NoOptionsMessage. Just search for a non-existent entry and see what I mean.
Any idea how this can be solved?
You can use this library
https://github.com/jacobworrel/react-windowed-select
Implements both react-select and react-window. Also, you can use the same properties that are available with react-select (https://react-select.com/props)
Every time I move my mouse over an option, this component runs filterOption()
as if something could possibly have changed other than focus.
I am genuinely perplexed by this performance issue and the maintainers' apparent apathy toward it. If performance tanks with 100+ items, it doesn't belong anywhere near a production environment... Virtualizing the list of options is like using a jackhammer to mount a poster to a wall — a bit much, to say the least.
I guess we need to find an alternative. Pretty infuriating that I'm noticing this issue long after having implemented this component into our design system.
Follow-up: Writing my own Option
component that strips out onMouseOver
and onMouseMove
props seems to work without breaking keyboard functionality. Maybe I'm missing something, but this thing still behaves the same without those props on my Options, which only further confuses me as to why they're on there in the first place 🤔 Thanks to @shunfan and @kotvasili
@aaronmw
The simple reality is that there is a lot of work to be done by what is essentially a handful of people who are doing so on their free time. Version 5 is still fairly recent and was a huge undertaking and it also forced the team to pause development to achieve parity between what was present in Flow translated into TypeScript. The last few weeks have been focused resolving issues found following the release.
Our next focus will likely be on addressing Menu behavior issues which I spent my weekend searching through open issues to identify and categorize. https://github.com/JedWatson/react-select/discussions/4864 We also would like to address the issues caused by Emotion with the hopes of creating an unstyled version that removes that dependency.
If you would like to contribute more, please feel free to open a discussion so we can talk about performance ideas in depth and in a holistic way that includes the full context of what's being done and why. There are some performance PR's that have been merged https://github.com/JedWatson/react-select/pull/4388 as well as others we'd like to revisit https://github.com/JedWatson/react-select/pull/4514, but our time is finite and would certainly welcome more collaboration.
@aaronmw Come on, it's not that much work to write a 20(ish)-line custom MenuList component:
import { List } from 'react-virtualized';
const MenuList = (props: any) => {
const {
width,
} = props.selectProps;
const rows = props.children;
const rowRenderer = ({
key, index, isScrolling, isVisible, style,
}: rowRendererProps) => (
<div key={key} style={style}>{rows[index]}</div>
);
return (
<List
width={width}
height={Math.min(45 * (rows.length || 0), 315)}
rowHeight={45}
rowCount={rows.length || 0}
rowRenderer={rowRenderer}
/>
);
};
This is a wonderful project and we should respect the fact people are doing this for free, so they make our lives easier.
Just wanted to build on the existing solutions -- this was the one I ended up on. Initial implementation was built off of the solution provided by @badwarlock https://github.com/JedWatson/react-select/issues/3128#issuecomment-847149453
I wanted to have something more keyboard navigable, but it turns out I needed to make it into a controlled component in order to get that to happen.
import React, { useState, useEffect, useRef } from "react";
import Select, { components, createFilter } from 'react-select';
import { Virtuoso } from 'react-virtuoso';
const InnerItem = React.memo(({ children }) => {
return <>{children}</>;
});
const NUMBER_ITEMS_VISIBLE = 6;
const ITEM_HEIGHT = 60;
const getListHeight = (length) => {
return length < NUMBER_ITEMS_VISIBLE ?
length * ITEM_HEIGHT :
NUMBER_ITEMS_VISIBLE * ITEM_HEIGHT;
};
const CustomMenuList = ({ options, children, getValue, hasValue, focusedOption, ...rest }) => {
const virtuosoRef = useRef(null);
const [initialFocus, setInitialFocus] = useState(false);
const [option] = getValue();
useEffect(() => {
let wasSetTrue = false;
if (virtuosoRef?.current) {
let selectedOptionIndex = 0;
// scroll to the selected option
if (option && !initialFocus) {
selectedOptionIndex = options.findIndex((item) => item.value === option.value);
wasSetTrue = true;
//scroll to the focused option
} else if (initialFocus && focusedOption) {
selectedOptionIndex = options.findIndex((item) => item.value === focusedOption.value);
}
virtuosoRef.current.scrollToIndex({
index: selectedOptionIndex,
align: "center",
behavior: "auto",
});
}
return () => {
// Component update to track that we can now scroll to whatever receives focus as opposed to the currently selected option
if (wasSetTrue) setInitialFocus(true);
}
}, [children, virtuosoRef, options, option, getValue, focusedOption, initialFocus]);
return Array.isArray(children) ? (
<Virtuoso
ref={virtuosoRef}
overscan={{ main: 12, reverse: 12 }}
style={{ height: `${getListHeight(children.length)}px` }}
totalCount={children.length}
itemContent={(index) => <InnerItem children={children[index]} />}
/>
) : (
<div>{children}</div>
);
};
const CustomOption = ({ children, ...props }) => {
// Remove the niceties for mouseover and mousemove to optimize for large lists
// eslint-disable-next-line no-unused-vars
const { onMouseMove, onMouseOver, ...rest } = props.innerProps;
const newProps = { ...props, innerProps: rest };
return (
<components.Option
{...newProps}
className="custom-option"
>
{children}
</components.Option>
);
};
/**
* BigSelect
*/
const BigSelect = React.memo((props) => {
const ref = useRef(null);
const { value, onChange, ...rest } = props;
return (
<Select
ref={ref}
{...rest}
classNamePrefix="big-select"
components={{
Option: CustomOption,
MenuList: CustomMenuList,
}}
captureMenuScroll={false}
filterOption={createFilter({ ignoreAccents: false })}
value={value}
onChange={(...args) => {
onChange(...args);
if (ref.current) ref.current.setState({ focusedOption: args[0] });
}}
/>
);
});
export default BigSelect;
CSS:
.custom-option.big-select__option {
transition: background 60ms;
min-height: 60px;
display: flex;
align-items: center;
}
.custom-option:hover {
transition-delay: 60ms;
background: #e2e6eb;
}
.custom-option.big-select__option--is-focused {
background: #e2e6eb;
}
.custom-option.big-select__option--is-selected {
background: #26c2ff;
}
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
If you guys are using pretty old version like in my case 1.2.0 directly pass ignoreAccents={false}
to Select component.
@ebonow
We also would like to address the issues caused by Emotion with the hopes of creating an unstyled version that removes that dependency.
What issues specifically, is this a render delay? I believe we're seeing an issue in a large form where potentially hundreds of selects can create a poor UX due to render delay. If this is related, is there an existing issue? I may want to contribute if I can navigate the emotion spaghetti and ripples it'll cause.
Hi everyone! I spent a few days solving the dropdown list freezing issue. I tried using the above described methods of creating
MenuList
usingreact-window
. I also added a customOption
component, removing propsonMouseMove
andonMouseOver
. Seems this fixed my problem.Example with
react-window
codesandbox.Update:
Example with
react-virtuoso
codesandbox.MenuList
import React from "react"; import { FixedSizeList as List } from "react-window"; const OPTION_HEIGHT = 40; const ROWS = 6; const MenuList = ({ options, children, getValue }) => { const [value] = getValue(); const initialOffset = options.indexOf(value) !== -1 ? Array.isArray(children) && children.length >= ROWS ? options.indexOf(value) >= ROWS ? options.indexOf(value) * OPTION_HEIGHT - OPTION_HEIGHT * 5 : 0 : 0 : 0; return Array.isArray(children) ? ( <List height={ children.length >= ROWS ? OPTION_HEIGHT * ROWS : children.length * OPTION_HEIGHT } itemCount={children.length} itemSize={OPTION_HEIGHT} initialScrollOffset={initialOffset} > {({ style, index }) => { return ( <div style={style}> {children[index]} </div> ); }} </List> ) : ( <div>{children}</div> ); }; export default MenuList;
Option
import React from "react"; import cx from "classnames"; const Option = ({ children, isSelected, innerProps }) => ( <div className={cx("react-select__option", { "react-select__option_selected": isSelected })} id={innerProps.id} tabIndex={innerProps.tabIndex} onClick={innerProps.onClick} > {children} </div> ); export default Option;
ReactSelect
import React from "react"; import Select from "react-select"; import MenuList from "./MenuList"; import Option from "./Option"; const ReactSelect = ({ options, value, onChange, placeholder }) => { return ( <Select options={options} value={value && [value]} onChange={onChange} classNamePrefix="react-select" placeholder={placeholder} components={{ MenuList, Option }} /> ); }; export default ReactSelect;
Works perfectly! Btw styling can be done inside the MenuList component like this:
<div style={{ ...style, height: 80, padding: 10, cursor: "pointer" }}> {children[index]} </div>
or like this: (tailwind itc)
<div style={style} className="hover:bg-main p-2 cursor-pointer"> {children[index]} </div>
Hi @VladimirMilenko,
In an effort to sustain the react-select project going forward, we're cleaning up and closing old issues and pull requests.
We understand this might be inconvenient but in the best interest of supporting the broader community we have to direct our efforts towards the current major version.
If you aren't using the latest version of react-select please consider upgrading to see if it resolves any issues you're having.
However, if you feel this issue is still relevant and you'd like us to review it - please leave a comment and we'll do our best to get back to you!
I have 50,000 items in my Select from react-select. I tried many things to improve performance: set ignoreAccents to false, disable onMouseMove and onMouseOver events and virtualization. But it's still slow.
Taking up @lynxtaa 's idea, I found the react-select-async-paginate library which allows you to "paginate" the items, and to retrieve the ones you want with the search.
The library : https://www.npmjs.com/package/react-select-async-paginate Example: https://codesandbox.io/s/10r1k12vk7?file=/src/App.jsx:673-688
I have just 1000 items, it's already very laggish.
i love how this thread is still going after all this time.
just ask GPTChat to write your implementation.
Or paste (a simplified version) of your implementation into GPTChat and ask it to correct. Tell it what's wrong. Show it where it hurts. It will help you.
🤖🤖🤖
Yeah, well, I was brought here in search of a built-in option for virtualization, honestly, I believe that a library that handles select dropdowns should have that option built-in. The fact that this is still going since 2018 should already tell everyone that there's real demand for it.
But since react-select seems to not be bothered by it, I just followed the react-window
approach.
Yeah, well, I was brought here in search of a built-in option for virtualization, honestly, I believe that a library that handles select dropdowns should have that option built-in. The fact that this is still going since 2018 should already tell everyone that there's real demand for it.
But since react-select seems to not be bothered by it, I just followed the
react-window
approach.
The entitlement of kids these days.
Yeah, well, I was brought here in search of a built-in option for virtualization, honestly, I believe that a library that handles select dropdowns should have that option built-in. The fact that this is still going since 2018 should already tell everyone that there's real demand for it. But since react-select seems to not be bothered by it, I just followed the
react-window
approach.The entitlement of kids these days.
Such a judgemental state of mind.
Yeah, well, I was brought here in search of a built-in option for virtualization, honestly, I believe that a library that handles select dropdowns should have that option built-in. The fact that this is still going since 2018 should already tell everyone that there's real demand for it.
I'm pretty sure this project is accepting PRs
There's a PR addressing the performance on large lists. The solution followed the memoization of buildCategorizedOptions rather than virtualization.
If you're referring to https://github.com/JedWatson/react-select/pull/5450, virtualization and disabling stripping diacritics have a much bigger impact on performance for large lists. That PR just makes it even faster assuming you're already doing those performance improvements.
Hi @VladimirMilenko,
In an effort to sustain the react-select project going forward, we're cleaning up and closing old issues and pull requests.
We understand this might be inconvenient but in the best interest of supporting the broader community we have to direct our efforts towards the current major version.
If you aren't using the latest version of react-select please consider upgrading to see if it resolves any issues you're having.
However, if you feel this issue is still relevant and you'd like us to review it - please leave a comment and we'll do our best to get back to you!
We’ve updated the tool from v2.x to v5.x last week and the performance issues are still there. I wanted to let you know.
i’ve seen couple of workarounds and tips how to improve the performance, we are going to look into them next week and will let you guys know our findings.
Edit: We did not check other options for performance gains apologies
ignoreAccents
if i am using the filterOption for custom logic, then how can i set ignoreAccents: i am using filterOption like this
filterOption={(option, inputValue) => {
if (option?.data?.text) {
return (
(
option.data.text
.toString()
.toLowerCase()
.match(inputValue.toString().toLowerCase()) || []
).length > 0
);
}
}}
Hi @vikrant-gembrill,
Same problem. Did you find a solution?
@timjahn93 did not find a solution to filter option issue but, what i found was react select was very slow when the developer tools was open in chrome. Also, if there is huge data, better use virtualization with react window. Also check your extensions.
@vikrant-gembrill Okay, I am using virtualization already, but using my own filter option, react select is too slow with high data and the page collapses. With createFilter and ignoreAccents it is way more performant.
Just as a reference for anyone else who might encounter this problem, the solution using filterOption works like magic: filterOption={createFilter({ ignoreAccents: false })}
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