import { propsAreEqual } from 'lib/helpers' import memoize from 'memoize-one' import { CSSProperties, ComponentType, MutableRefObject, ReactNode, memo, useRef } from 'react' import AutoSizer from 'react-virtualized-auto-sizer' import { VariableSizeList, areEqual } from 'react-window' import InfiniteLoader from 'react-window-infinite-loader' import { Skeleton } from 'ui' /** * Note that the loading more logic of this component works best with a cursor-based * pagination API such that each payload response from the API returns a structure like * { cursor, items, hasNext, hasPrevious } */ const createItemData = memoize((items, itemProps) => ({ items, ...itemProps })) export type ItemRenderer = ComponentType< { item: T listRef: MutableRefObject | null | undefined> index: number } & P > export interface ItemProps { data: { items: T[] itemProps: P ItemComponent: ItemRenderer listRef: MutableRefObject | null | undefined> LoaderComponent?: ReactNode } index: number style: CSSProperties } export interface InfiniteListProps { items?: T[] itemProps?: P hasNextPage?: boolean isLoadingNextPage?: boolean getItemSize?: (index: number) => number onLoadNextPage?: () => void ItemComponent?: ItemRenderer LoaderComponent?: ReactNode } const Item = memo(({ data, index, style }: ItemProps) => { const { items, itemProps, ItemComponent, listRef, LoaderComponent } = data const item = index < items.length ? items[index] : undefined return item ? (
) : LoaderComponent !== undefined ? (
{LoaderComponent}
) : (
) }, areEqual) Item.displayName = 'Item' function InfiniteList({ items = [], itemProps, hasNextPage = false, isLoadingNextPage = false, getItemSize = () => 40, onLoadNextPage = () => {}, ItemComponent = () => null, LoaderComponent, }: InfiniteListProps) { const listRef = useRef | null>() // Only load 1 page of items at a time // Pass an empty callback to InfiniteLoader in case it asks to load more than once const loadMoreItems = isLoadingNextPage ? () => {} : onLoadNextPage // Every row is loaded except for our loading indicator row const isItemLoaded = (index: number) => { return !hasNextPage || index < items.length } const itemCount = hasNextPage ? items.length + 1 : items.length const itemData = createItemData(items, { itemProps, ItemComponent, LoaderComponent, listRef }) return ( <>
{({ height, width }: { height: number; width: number }) => ( {({ onItemsRendered, ref }) => ( { ref(refy) listRef.current = refy }} height={height ?? 0} width={width ?? 0} itemCount={itemCount} itemData={itemData} itemSize={getItemSize} onItemsRendered={onItemsRendered} > {Item} )} )}
) } // memo erases generics so this magic is needed export default memo(InfiniteList, propsAreEqual) as typeof InfiniteList