question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Autocomplete] Slow despite virtualisation

See original GitHub issue
  • The issue is present in the latest release.
  • I have searched the issues of this repository and believe that this is not a duplicate.

Current Behavior 😯

Autocomplete maps over groupedOptions and creates React elements where the number of items can be tens of thousands (e.g. list of stocks): https://github.com/mui-org/material-ui/blob/f23207bf2312eba2686522f904d72aea7d3c0a88/packages/material-ui/src/Autocomplete/Autocomplete.js#L670

Turns out this is quite slow and can take >1s to simply open the listbox when clicking on the Autocomplete input:

Autocomplete

image

Check line 456 which is where the .map call takes place.

image

Expected Behavior 🤔

I believe that Autocomplete should accept a renderListbox render prop that would allow the consumer to be in control of how it’s rendered. I managed to hack something together in node_modules, and you can see the interaction is a lot faster:

Autocomplete with renderListbox

Steps to Reproduce 🕹

Steps:

  1. Navigate to https://codesandbox.io/s/virtualize-material-demo-forked-6hdqd?file=/demo.tsx which is a fork of the Virtualized example but with the number of options increased from 10,000 to 26,000
  2. Click on the input
  3. Observe a significant delay before the listbox opens

Context 🔦

I’m trying to render a rather large list of options, and was surprised that virtualisation didn’t help much. That being said, the UX is pretty terrible with that many options anyway, and maybe I should use limit option of createFilterOptions instead.

Your Environment 🌎

`npx @material-ui/envinfo`
  System:
    OS: macOS 10.15.7
  Binaries:
    Node: 12.18.2 - /usr/local/bin/node
    Yarn: 1.22.4 - /usr/local/bin/yarn
    npm: 6.14.5 - /usr/local/bin/npm
  Browsers:
    Chrome: 89.0.4389.90
  npmPackages:
    @emotion/react: ^11.1.5 => 11.1.5 
    @emotion/styled: ^11.1.5 => 11.1.5 
    @material-ui/core: 5.0.0-alpha.27 => 5.0.0-alpha.27 
    @material-ui/lab: 5.0.0-alpha.27 => 5.0.0-alpha.27 
    @material-ui/styled-engine:  5.0.0-alpha.25 
    @material-ui/styles:  5.0.0-alpha.27 
    @material-ui/system:  5.0.0-alpha.27 
    @material-ui/types:  5.1.7 
    @material-ui/unstyled:  5.0.0-alpha.27 
    @material-ui/utils:  5.0.0-alpha.27 
    @types/react: ^17.0.0 => 17.0.3 
    react: ^17.0.1 => 17.0.1 
    react-dom: ^17.0.1 => 17.0.1 
    typescript: ^4.1.3 => 4.2.3 

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:5
  • Comments:12 (12 by maintainers)

github_iconTop GitHub Comments

2reactions
oliviertassinaricommented, Mar 21, 2021

@NMinhNguyen Thanks for opening the issue. I have tried to render 100,000 rows. It’s REALLY slow. It takes about 10s to open in dev mode. This is pretty interesting as it’s slightly counterintuitive. I always thought that creating a LOT of elements with React was cheap. Anyway, here is how to get the demo render x30 faster:

diff --git a/docs/src/pages/components/autocomplete/Virtualize.tsx b/docs/src/pages/components/autocomplete/Virtualize.tsx
index d708dc87b9..32fe0533ad 100644
--- a/docs/src/pages/components/autocomplete/Virtualize.tsx
+++ b/docs/src/pages/components/autocomplete/Virtualize.tsx
@@ -1,8 +1,6 @@
 import * as React from 'react';
 import TextField from '@material-ui/core/TextField';
-import Autocomplete, {
-  AutocompleteRenderGroupParams,
-} from '@material-ui/core/Autocomplete';
+import Autocomplete from '@material-ui/core/Autocomplete';
 import useMediaQuery from '@material-ui/core/useMediaQuery';
 import ListSubheader from '@material-ui/core/ListSubheader';
 import { useTheme, makeStyles } from '@material-ui/core/styles';
@@ -13,12 +11,27 @@ const LISTBOX_PADDING = 8; // px

 function renderRow(props: ListChildComponentProps) {
   const { data, index, style } = props;
-  return React.cloneElement(data[index], {
-    style: {
-      ...style,
-      top: (style.top as number) + LISTBOX_PADDING,
-    },
-  });
+  const dataSet = data[index];
+
+  if (dataSet.hasOwnProperty('group')) {
+    return (
+      <ListSubheader key={dataSet.key} component="div">
+        {dataSet.group}
+      </ListSubheader>
+    );
+  }
+
+  return (
+    <li
+      {...dataSet[0]}
+      style={{
+        ...style,
+        top: (style.top as number) + LISTBOX_PADDING,
+      }}
+    >
+      <Typography noWrap>{dataSet[1]}</Typography>
+    </li>
+  );
 }

 const OuterElementContext = React.createContext({});
@@ -44,7 +57,10 @@ const ListboxComponent = React.forwardRef<HTMLDivElement>(function ListboxCompon
   ref,
 ) {
   const { children, ...other } = props;
-  const itemData = React.Children.toArray(children);
+  const itemData = (children as any).reduce((acc: any, item: any) => {
+    acc.push(item);
+    return acc.concat(item.children);
+  }, []);
   const theme = useTheme();
   const smUp = useMediaQuery(theme.breakpoints.up('sm'), {
     noSsr: true,
@@ -52,8 +68,8 @@ const ListboxComponent = React.forwardRef<HTMLDivElement>(function ListboxCompon
   const itemCount = itemData.length;
   const itemSize = smUp ? 36 : 48;

-  const getChildSize = (child: React.ReactNode) => {
-    if (React.isValidElement(child) && child.type === ListSubheader) {
+  const getChildSize = (child: any) => {
+    if (child.hasOwnProperty('group')) {
       return 48;
     }

@@ -64,7 +80,7 @@ const ListboxComponent = React.forwardRef<HTMLDivElement>(function ListboxCompon
     if (itemCount > 8) {
       return 8 * itemSize;
     }
-    return itemData.map(getChildSize).reduce((a, b) => a + b, 0);
+    return itemData.map(getChildSize).reduce((a: number, b: number) => a + b, 0);
   };

   const gridRef = useResetCache(itemCount);
@@ -116,13 +132,6 @@ const OPTIONS = Array.from(new Array(10000))
   .map(() => random(10 + Math.ceil(Math.random() * 20)))
   .sort((a: string, b: string) => a.toUpperCase().localeCompare(b.toUpperCase()));

-const renderGroup = (params: AutocompleteRenderGroupParams) => [
-  <ListSubheader key={params.key} component="div">
-    {params.group}
-  </ListSubheader>,
-  params.children,
-];
-
 export default function Virtualize() {
   const classes = useStyles();

@@ -135,15 +144,11 @@ export default function Virtualize() {
       ListboxComponent={
         ListboxComponent as React.ComponentType<React.HTMLAttributes<HTMLElement>>
       }
-      renderGroup={renderGroup}
       options={OPTIONS}
       groupBy={(option) => option[0].toUpperCase()}
       renderInput={(params) => <TextField {...params} label="10,000 options" />}
-      renderOption={(props, option) => (
-        <li {...props}>
-          <Typography noWrap>{option}</Typography>
-        </li>
-      )}
+      renderOption={(props, option) => [props, option]}
+      renderGroup={(params) => params}
     />
   );
 }

I have said F**K to TypeScript with the any. If somebody wants to look into how to get better types coverage, that would be great.

1reaction
eps1loncommented, Jul 27, 2021

Can we avoid doing toArray alltogether and just recommend using <Autocomplete>{[ <A />, <B />]}</Autocomplete>? We expect a flat list anyway and re-keying shouldn’t be necessary. Or maybe still use toArray but add a warning at >100 items that you should just create the array instead?

Seems like toArray uses mapChildren internally which uses a bunch of needless .call and .apply which are probably responsible for the slowness.

Read more comments on GitHub >

github_iconTop Results From Across the Web

1815895 – dnf autocomplete too slow - Red Hat Bugzilla
Description of problem: dnf is taking too much time (many seconds) to autocomplete packages names with <tab>. Version: 4.2.19 Steps to Reproduce: Type:...
Read more >
Autocomplete when typing is really slow. #49260 - GitHub
Issue Type: Performance Issue Autocomplete when typing is really slow. I have to wait for at least 5 seconds to see the suggestions....
Read more >
Intellisense too slow - bad auto completion results
Typing slower I see that IS shows the drop list and scrolls to G4x4 and even though I have typed (and see the...
Read more >
Autocomplete search slow when first time search - Telerik
if has a lot of data when first time type to search is very slow ,but after will quickly more. how to fix...
Read more >
Virtual Machine Slow Performance—Monitoring Issues ...
SolarWinds ® Virtualization Manager (VMAN) is a comprehensive virtual machine monitoring and management solution capable of helping you resolve ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found