InfiniteVirtualList.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import type { CSSProperties, FC } from 'react'
  2. import React from 'react'
  3. import { FixedSizeList as List } from 'react-window'
  4. import InfiniteLoader from 'react-window-infinite-loader'
  5. import SegmentCard from './SegmentCard'
  6. import s from './style.module.css'
  7. import type { SegmentDetailModel } from '@/models/datasets'
  8. type IInfiniteVirtualListProps = {
  9. hasNextPage?: boolean // Are there more items to load? (This information comes from the most recent API request.)
  10. isNextPageLoading: boolean // Are we currently loading a page of items? (This may be an in-flight flag in your Redux store for example.)
  11. items: Array<SegmentDetailModel[]> // Array of items loaded so far.
  12. loadNextPage: () => Promise<void> // Callback function responsible for loading the next page of items.
  13. onClick: (detail: SegmentDetailModel) => void
  14. onChangeSwitch: (segId: string, enabled: boolean) => Promise<void>
  15. onDelete: (segId: string) => Promise<void>
  16. archived?: boolean
  17. }
  18. const InfiniteVirtualList: FC<IInfiniteVirtualListProps> = ({
  19. hasNextPage,
  20. isNextPageLoading,
  21. items,
  22. loadNextPage,
  23. onClick: onClickCard,
  24. onChangeSwitch,
  25. onDelete,
  26. archived,
  27. }) => {
  28. // If there are more items to be loaded then add an extra row to hold a loading indicator.
  29. const itemCount = hasNextPage ? items.length + 1 : items.length
  30. // Only load 1 page of items at a time.
  31. // Pass an empty callback to InfiniteLoader in case it asks us to load more than once.
  32. const loadMoreItems = isNextPageLoading ? () => { } : loadNextPage
  33. // Every row is loaded except for our loading indicator row.
  34. const isItemLoaded = (index: number) => !hasNextPage || index < items.length
  35. // Render an item or a loading indicator.
  36. const Item = ({ index, style }: { index: number; style: CSSProperties }) => {
  37. let content
  38. if (!isItemLoaded(index)) {
  39. content = (
  40. <>
  41. {[1, 2, 3].map(v => (
  42. <SegmentCard loading={true} detail={{ position: v } as any} />
  43. ))}
  44. </>
  45. )
  46. }
  47. else {
  48. content = items[index].map(segItem => (
  49. <SegmentCard
  50. key={segItem.id}
  51. detail={segItem}
  52. onClick={() => onClickCard(segItem)}
  53. onChangeSwitch={onChangeSwitch}
  54. onDelete={onDelete}
  55. loading={false}
  56. archived={archived}
  57. />
  58. ))
  59. }
  60. return (
  61. <div style={style} className={s.cardWrapper}>
  62. {content}
  63. </div>
  64. )
  65. }
  66. return (
  67. <InfiniteLoader
  68. itemCount={itemCount}
  69. isItemLoaded={isItemLoaded}
  70. loadMoreItems={loadMoreItems}
  71. >
  72. {({ onItemsRendered, ref }) => (
  73. <List
  74. ref={ref}
  75. className="List"
  76. height={800}
  77. width={'100%'}
  78. itemSize={200}
  79. itemCount={itemCount}
  80. onItemsRendered={onItemsRendered}
  81. >
  82. {Item}
  83. </List>
  84. )}
  85. </InfiniteLoader>
  86. )
  87. }
  88. export default InfiniteVirtualList