3

I've been trying to implement virtualization to a React-Select component although without success. Every single virtualization package that I have tried using has had some sort of breaking fault that I haven't been able to solve, to be precise:

  • react-select-virtualized: Graphical bugs, Selection doesn't work at all
  • react-virtualized: For some reason, still slightly choppy, also breaking graphical bugs (Like any text that would be in a second row shows up in the next Option)
  • react-window: Couldn't get it to work at all
  • react-virtual: Very choppy (might not be working at all)
  • react-virtuoso: Still very choppy and unusable (although slightly better than normal, but not good enough for prod.)

It could be that I'm messing something up due to my lack of experience, but even when directly copy-pasting other people's solutions, I end up with a buggy mess. I believe this has to do with most of the virtualization packages above not being fully adapted for select v5 or TypeScript or React 18 etc.

Any help would be more than appreciated!! Thank you in advance :)

1 Answer 1

2

Answer related to this point: react-virtualized: For some reason, still slightly choppy, also breaking graphical bugs (Like any text that would be in a second row shows up in the next Option)


To prevent second row shows up in the next option you need a CellMeasurerCache

When list size (width) changes it will automatically recalculate height of each row in visible frame.

Of course it not a perfect solution but it hits your requirements

import React, { useMemo } from 'react'
import AsyncSelect from 'react-select/async'
import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from 'react-virtualized'
import type { MenuListProps, GroupBase } from 'react-select'
import type { ListRowProps } from 'react-virtualized'

type ListOption = {
  label: string
  value: string
}

const VirtualizedList = ({ children }: MenuListProps<ListOption, false, GroupBase<ListOption>>) => {
  const rows = children

  const cellCache = useMemo(() => {
    return new CellMeasurerCache({
      fixedWidth: true,
      defaultHeight: 30
    })
  }, [])

  if (!Array.isArray(rows)) {
    // For children like: "Loading" or "No Options" provided by 'react-select'
    return <>{children}</>
  }

  const rowRenderer = ({ key, parent, index, style }: ListRowProps) => (
    <CellMeasurer cache={cellCache} key={key} columnIndex={0} rowIndex={index} parent={parent}>
      <div key={key} style={style}>
        {rows[index]}
      </div>
    </CellMeasurer>
  )

  return (
    <div style={{ height: '300px' }}>
      <AutoSizer>
        {({ width, height }) => (
          <List
            width={width}
            height={height}
            deferredMeasurementCache={cellCache}
            rowHeight={cellCache.rowHeight}
            rowCount={rows.length}
            rowRenderer={rowRenderer}
          />
        )}
      </AutoSizer>
    </div>
  )
}

export const Example = () => {
  return (
    <div>
      <AsyncSelect
        cacheOptions
        components={{ MenuList: VirtualizedList }}
        defaultOptions={'YOUR DEFAULT DATA'}
        loadOptions={'YOUR DATA FROM SOMEWHERE'}
        styles={{
          menu: ({ position, fontWeight, ...provided }) => ({
            ...provided,
            position: 'static'
          })
        }}
      />
    </div>
  )
}

Example with cache recalculation triggered by changing of row width

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.