<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 = []

    let keys = []
    let grouped = groupBySortedKeys(groupBy, data, keys)

    keys.forEach((group) => {
      flatData.push({ __isGroup: group }, ...grouped[group])
    }, [])

    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
          ref={ref}
          className={cn(className, 'rw-virtual-list')}
          emptyListMessage={messages.emptyList({})}
        >
          {items}
        </div>
    )
      this.move()
  }

  renderItems = (items, ref) => {
    let { className, messages } = this.props

      return (
        <OptionComponent
          key={key}
          activeId={activeId}
          dataItem={item}
          // disabled={isDisabled(item)}
      </Listbox>
    )
  }

  renderItem = (index, key) => {
    let {
      activeId,
        </OptionComponent>
      selectedItem,
      onSelect,
      dataState,
      renderItem,
      isDisabled,
      pageSize,
      hasNextPage,
      onRequestItems,
      searchTerm,
      renderGroup,
      loadingComponent: LoadingComponent,
      optionComponent: OptionComponent,
    } = this.props

      if (idx !== -1) {
        innerRef.current.scrollAround(idx)
      }
    })

    useEffect(() => {
      scrollIntoView()
    }, [listCtx.focusedItem])

    useImperativeHandle(
      ref,
      () => ({
        scrollIntoView,
      }),
      [scrollIntoView],
    )

    return (
      <div
        className={cn(
          'rw-virtual-list-wrapper',
          type === 'uniform' && 'rw-virtual-list-fixed',
        )}
    if (item && item.__isGroup) {
      return (
        <ListOptionGroup key={key}>
          {renderGroup({ group: item.group, searchTerm })}
        </ListOptionGroup>
      )
    }
          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}
        selected={selectedItem === item}
        onSelect={onSelect}
      >
        {renderItem({ item, index, searchTerm })}
      </OptionComponent>
    )
  }
)

export default VirtualList

<MSG> fix: virtualized list api assumptions

<DFF> @@ -128,6 +128,34 @@ class VirtualList extends React.Component {
     )
       this.move()
   }
+  _renderItem = ({ item, ...rest }) => {
+    const { isDisabled, renderItem, textAccessor, valueAccessor } = this.props
+    let Component = this.props.itemComponent
+    if (renderItem) {
+      return renderItem({ item, ...rest })
+    } else if (Component) {
+      return (
+        <Component
+          item={item}
+          value={valueAccessor(item)}
+          text={textAccessor(item)}
+          disabled={isDisabled(item)}
+          {...rest}
+        />
+      )
+    }
+    return textAccessor(item)
+  }
+
+  _renderGroup = (group, searchTerm) => {
+    const { renderGroup, groupComponent: Component } = this.props
+    if (renderGroup) {
+      return renderGroup({ group, searchTerm })
+    } else if (Component) {
+      return <Component item={group} searchTerm={searchTerm} />
+    }
+    return group
+  }
 
   renderItems = (items, ref) => {
     let { className, messages } = this.props
@@ -141,7 +169,6 @@ class VirtualList extends React.Component {
       </Listbox>
     )
   }
-
   renderItem = (index, key) => {
     let {
       activeId,
@@ -149,13 +176,11 @@ class VirtualList extends React.Component {
       selectedItem,
       onSelect,
       dataState,
-      renderItem,
       isDisabled,
       pageSize,
       hasNextPage,
       onRequestItems,
       searchTerm,
-      renderGroup,
       loadingComponent: LoadingComponent,
       optionComponent: OptionComponent,
     } = this.props
@@ -186,7 +211,7 @@ class VirtualList extends React.Component {
     if (item && item.__isGroup) {
       return (
         <ListOptionGroup key={key}>
-          {renderGroup({ group: item.group, searchTerm })}
+          {this._renderGroup({ group: item.group, searchTerm })}
         </ListOptionGroup>
       )
     }
@@ -203,7 +228,7 @@ class VirtualList extends React.Component {
         selected={selectedItem === item}
         onSelect={onSelect}
       >
-        {renderItem({ item, index, searchTerm })}
+        {this._renderItem({ item, index, searchTerm })}
       </OptionComponent>
     )
   }

fix: virtualized list api assumptions