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:
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 :)
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