<NME> VirtualList.js
<BEF> /* eslint-disable react/prop-types */
import cn from 'classnames'
import PropTypes from 'prop-types'
import React, {
  useContext,
  useEffect,
  useMemo,
  useRef,
  useImperativeHandle,
} from 'react'
import useEventCallback from '@restart/hooks/useEventCallback'
import { useAccessors } from 'react-widgets/Accessors'
import BaseVirtualList from 'react-list'
// import Listbox from 'react-widgets/BaseListbox'
import ListOption from 'react-widgets/ListOption'
import { FocusListContext } from 'react-widgets/FocusListContext'
import ListOptionGroup from 'react-widgets/ListOptionGroup'
import { groupBySortedKeys, toItemArray } from 'react-widgets/_'

export const virtualListPropTypes = {
  itemSizeEstimator: PropTypes.func,
  itemSizeGetter: PropTypes.func,
  pageSize: PropTypes.number,
  threshold: PropTypes.number,
  type: PropTypes.oneOf(['simple', 'variable', 'uniform']),
  useStaticSize: PropTypes.bool,
  useTranslate3d: PropTypes.bool,
  hasNextPage: PropTypes.bool,
  onRequestItems: PropTypes.func,
  loadingComponent: PropTypes.elementType,
}

function useFlattenedData(data, groupBy) {
  return useMemo(() => {
    if (!groupBy) return data

    const flatData = []

    selectedItem: PropTypes.any,

    isDisabled: PropTypes.func.isRequired,
    textAccessor: PropTypes.func.isRequired,

    disabled: CustomPropTypes.disabled.acceptsArray,
    return flatData
  }, [data, groupBy])
}

export function getVirtualListProps({
  type,
  itemSizeGetter,
  itemSizeEstimator,
  pageSize = 20,
  threshold = 300,
  useStaticSize,
  useTranslate3d,
  onRequestItems,
  hasNextPage,
  loadingComponent,
  ...props
}) {
  return {
    props,
    listProps: {
      onRequestItems,
      hasNextPage,
      loadingComponent,
      type,
      itemSizeGetter,
      itemSizeEstimator,
      pageSize,
      threshold,
      useStaticSize,
      useTranslate3d,
    },
  }
}

const VirtualList = React.forwardRef(
  (
    {
      data,
      activeId,
      focusedItem,
      selectedItem,
      onSelect,
      renderItem,
      textField,
      dataKey,

      disabled,
      pageSize,
      onRequestItems,
      searchTerm,
      renderGroup,
      className,
      messages,
      itemSizeGetter,
      itemSizeEstimator,
      threshold,
      useStaticSize,
      useTranslate3d = true,
      itemHeight,
      groupBy,
      type = 'variable',
      hasNextPage = false,
      loadingComponent: LoadingComponent,
      optionComponent: OptionComponent = ListOption,
    },
    ref,
  ) => {
    const innerRef = useRef()
    const accessors = useAccessors(textField, dataKey)
    const listCtx = useContext(OptionsContext)
    const flatData = useFlattenedData(data, groupBy)
    const disabledItems = toItemArray(disabled)

    function renderListbox(items, ref) {
      return (
        <div
  }

  componentDidUpdate(prevProps) {
    if (
      prevProps.focusedItem !== this.props.focusedItem ||
      prevProps.selectedItem !== this.props.selectedItem
    )
      this.move()
  }
      let item = flatData[index]
      let len = flatData.length

      if (hasNextPage === true && index >= len) {
        if (onRequestItems)
          onRequestItems({
            pageSize,
            searchTerm,
            limit: len + pageSize,
            currentIndex: index,
          })
    )
  }

  renderGroupHeader(group, key) {
    var renderGroup = this.props.renderGroup
    return
  }

  renderItem = (index, key) => {
    let {
      activeId,
              'Loading items…'
            )}
          </li>
        )
      }

      if (item && item.__isGroup) {
        return (
          <ListOptionGroup key={key}>
            {renderGroup({ group: item.group, searchTerm })}
          </ListOptionGroup>
        )
      }

      return (
        <OptionComponent
          key={key}
          activeId={activeId}
          dataItem={item}
          // disabled={isDisabled(item)}
          selected={selectedItem === item}
          onSelect={onSelect}
        >
          {renderItem
            ? renderItem({ item, index, searchTerm })
            : accessors.text(item)}
        </OptionComponent>
      )
    }

    let length = flatData.length

    if (hasNextPage === true) length += 1

    const scrollIntoView = useEventCallback(() => {
      if (!innerRef.current) return

      let idx = flatData.indexOf(listCtx?.focusedItem)
    if (item && item.__isGroup) {
      return (
        <ListOptionGroup key={key}>
          {renderGroup({ group: item.group })}
        </ListOptionGroup>
      )
    }
      scrollIntoView()
    }, [listCtx.focusedItem])

    useImperativeHandle(
      ref,
      () => ({
        scrollIntoView,
      }),
      [scrollIntoView],
    )
        selected={selectedItem === item}
        onSelect={onSelect}
      >
        {renderItem({ item, index })}
      </OptionComponent>
    )
  }
      >
        <BaseVirtualList
          ref={innerRef}
          type={type}
          length={length}
          pageSize={pageSize}
          threshold={threshold}
          useStaticSize={useStaticSize}
          useTranslate3d={useTranslate3d}
          itemRenderer={renderOption}
          itemsRenderer={renderListbox}
          itemSizeGetter={itemSizeGetter}
          itemSizeEstimator={itemSizeEstimator}
          // these are all to break the list's SCU
          focusedItem={focusedItem}
          selectedItem={selectedItem}
          searchTerm={searchTerm}
          onSelect={onSelect}
          disabled={disabled}
          itemHeight={itemHeight}
        />
      </div>
    )
  },
)

export default VirtualList

<MSG> clean up autocomplete

<DFF> @@ -39,6 +39,7 @@ class VirtualList extends React.Component {
     selectedItem: PropTypes.any,
 
     isDisabled: PropTypes.func.isRequired,
+    valueAccessor: PropTypes.func.isRequired,
     textAccessor: PropTypes.func.isRequired,
 
     disabled: CustomPropTypes.disabled.acceptsArray,
@@ -121,9 +122,10 @@ class VirtualList extends React.Component {
   }
 
   componentDidUpdate(prevProps) {
+    const { valueAccessor, focusedItem, selectedItem } = this.props
     if (
-      prevProps.focusedItem !== this.props.focusedItem ||
-      prevProps.selectedItem !== this.props.selectedItem
+      valueAccessor(prevProps.focusedItem) !== valueAccessor(focusedItem) ||
+      valueAccessor(prevProps.selectedItem) !== valueAccessor(selectedItem)
     )
       this.move()
   }
@@ -141,11 +143,6 @@ class VirtualList extends React.Component {
     )
   }
 
-  renderGroupHeader(group, key) {
-    var renderGroup = this.props.renderGroup
-    return
-  }
-
   renderItem = (index, key) => {
     let {
       activeId,
@@ -190,7 +187,7 @@ class VirtualList extends React.Component {
     if (item && item.__isGroup) {
       return (
         <ListOptionGroup key={key}>
-          {renderGroup({ group: item.group })}
+          {renderGroup({ group: item.group, searchTerm })}
         </ListOptionGroup>
       )
     }
@@ -207,7 +204,7 @@ class VirtualList extends React.Component {
         selected={selectedItem === item}
         onSelect={onSelect}
       >
-        {renderItem({ item, index })}
+        {renderItem({ item, index, searchTerm })}
       </OptionComponent>
     )
   }

clean up autocomplete