7

I'm using the Autocomplete component from Material-UI in a project. Since I have a lot of options to render, virtualization would be very beneficial. So I started from the virtualized example in the docs with react-window. Everything worked great, but the project already has a dependency on react-virtualized and I would like to avoid adding a new one that solves something similar. So based on the react-window example, I tried to re-implement it using List from react-virtualized.

Code Sample

https://codesandbox.io/s/sleepy-hypatia-igog8?fontsize=14&hidenavigation=1&module=%2Fsrc%2FVirtualizedAutocomplete.js&theme=dark

The Problem

As demonstrated in the sandbox above, it's kind of working. What doesn't work is the keyboard navigation. You can navigate using the keyboard, but the Listbox doesn't scroll to the highlighted value when you navigate outside of the visible elements.

What I've tried:

  • Overriding onKeyDown for the Autocomplete component. This however completely overrides the keydown functionality, requiring me to re-implement it.
  • Using onKeyUp to keep the onKeyDown functionality and finding the element with the data-focus attribute, and then using scrollToIndex on the List component. This works for some cases, but if the user holds down the key for a while and ends up on an index outside of the overscan rendered items it breaks.

Does anyone have a good way of using react-virtualized to virtualize the options in the Material-UI Autoselect component? Should I be using something other than List?

My final resort would be to use the useAutocomplete hook and just re-implement the rendering logic, but since I'm only after the virtualization I'd like to avoid this if possible.

Mikaela
  • 526
  • 1
  • 4
  • 11

2 Answers2

16

I managed to get it working thanks to an answer in an issue I opened about this on the Material-UI repo.

To get the scroll functionality working you need to make sure that the scrolling element has the role of "listbox".

This is an updated example of the code demonstrating a working version: https://codesandbox.io/s/adoring-saha-n9cr1

The only thing changed is extracting the role property from the ListboxComponent props and assigning it to the List component.

Mikaela
  • 526
  • 1
  • 4
  • 11
  • 2
    As an addition to this, the maintainer of `react-virtualized` suggests using `react-window` instead as it's a much much lighter library. In most cases you can use it instead of `react-virtualized`. – Big Money Sep 25 '20 at 06:02
  • Trying something similar with react-virtuoso - seems I have the `role` prop but still can't get the keyboard presses to work properly. Any other ideas? https://stackoverflow.com/questions/69060738/material-ui-autocomplete-virtualization-w-react-virtuoso – Mbrevda Sep 09 '21 at 08:21
4
   /* eslint-disable react/prop-types */
   import * as React from "react";
   import { VariableSizeList } from "react-window";
                                
    const Row = ({ data, index, style }) => {
                                    const elem = data[index];
                                    return (
                                        <div style={style}>
                                            <React.Fragment>{elem}</React.Fragment>
                                        </div>
                                    );
                                };
                                
                                const useResetCache = (data: any) => {
                                    const ref = React.useRef<VariableSizeList>(null);
                                    React.useEffect(() => {
                                        if (ref.current !== null) {
                                            ref.current.resetAfterIndex(0, true);
                                        }
                                    }, [data]);
                                    return ref;
                                };
                                
                                const OuterElementContext = React.createContext({});
                                // eslint-disable-next-line react/display-name
                                const OuterElementType = React.forwardRef<HTMLDivElement>((props, ref) => {
                                    const outerProps = React.useContext(OuterElementContext);
                                    return <div ref={ref} {...props} {...outerProps} />;
                                });
   // eslint-disable-next-line react/display-name
    export const VirtualizedList = React.forwardRef((props: any, ref: any) => {
                                    const itemCount = props.children.length;
                                    const gridRef = useResetCache(itemCount);
                                    const outerProps = { ...props };
                                    delete outerProps.children;
                                    return (
                                        <div ref={ref}>
                                            <OuterElementContext.Provider value={outerProps}>
                                                <VariableSizeList
                                                    ref={gridRef}
                                                    outerElementType={OuterElementType}
                                                    className="List"
                                                    height={400}
                                                    itemCount={itemCount}
                                                    itemSize={() => props.rowheight}
                                                    overscanCount={5}
                                                    itemData={{ ...props.children }}
                                                >
                                                    {Row}
                                                </VariableSizeList>
                                            </OuterElementContext.Provider>
                                        </div>
                                    );
                                });
                            

//And then use it like this

import { VirtualizedList } from "./VirtualListe";                                    
const VirtualizedListComp = (defaultprops: any, ref) => {
return <VirtualizedList ref={ref} {...defaultprops} rowheight= 
{48} />;
                  };
                        
const listboxComponent = props.usevertualization ? 
React.forwardRef(VirtualizedListComp) : undefined;
                    
 <Autocomplete ... ListboxComponent={listboxComponent} .../>