diff --git a/src/client/components/SimpleVirtualList.tsx b/src/client/components/SimpleVirtualList.tsx new file mode 100644 index 0000000..7768ed5 --- /dev/null +++ b/src/client/components/SimpleVirtualList.tsx @@ -0,0 +1,69 @@ +import { useWatch } from '@/hooks/useWatch'; +import { VirtualItem, useVirtualizer } from '@tanstack/react-virtual'; +import { Empty } from 'antd'; +import { last } from 'lodash-es'; +import React, { useRef } from 'react'; + +interface VirtualListProps { + allData: T[]; + hasNextPage: boolean | undefined; + isFetchingNextPage: boolean; + onFetchNextPage: () => void; + estimateSize: (index: number) => number; + renderItem: (item: VirtualItem) => React.ReactElement; +} +export const SimpleVirtualList: React.FC = React.memo( + (props) => { + const { + allData, + hasNextPage, + isFetchingNextPage, + onFetchNextPage, + estimateSize, + renderItem, + } = props; + + const parentRef = useRef(null); + + const rowVirtualizer = useVirtualizer({ + count: hasNextPage ? allData.length + 1 : allData.length, + getScrollElement: () => parentRef.current, + estimateSize, + overscan: 5, + }); + + const virtualItems = rowVirtualizer.getVirtualItems(); + + useWatch([virtualItems], () => { + const lastItem = last(virtualItems); + + if (!lastItem) { + return; + } + + if ( + lastItem.index >= allData.length - 1 && + hasNextPage && + !isFetchingNextPage + ) { + onFetchNextPage(); + } + }); + + return ( +
+ {virtualItems.length === 0 && } + +
+ {virtualItems.map((virtualItem) => renderItem(virtualItem))} +
+
+ ); + } +); +SimpleVirtualList.displayName = 'SimpleVirtualList'; diff --git a/src/client/routes/settings/auditLog.tsx b/src/client/routes/settings/auditLog.tsx index 7e3e188..f9564ff 100644 --- a/src/client/routes/settings/auditLog.tsx +++ b/src/client/routes/settings/auditLog.tsx @@ -13,6 +13,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import { useWatch } from '@/hooks/useWatch'; import dayjs from 'dayjs'; import { ColorTag } from '@/components/ColorTag'; +import { SimpleVirtualList } from '@/components/SimpleVirtualList'; export const Route = createFileRoute('/settings/auditLog')({ beforeLoad: routeAuthBeforeLoad, @@ -66,57 +67,53 @@ function PageComponent() { }> -
- {virtualItems.length === 0 && } + 48} + renderItem={(item) => { + const isLoaderRow = item.index > allData.length - 1; + const data = allData[item.index]; -
- {virtualItems.map((virtualRow) => { - const isLoaderRow = virtualRow.index > allData.length - 1; - const item = allData[virtualRow.index]; - - return ( - - {isLoaderRow ? ( - hasNextPage ? ( - t('Loading more...') - ) : ( - t('Nothing more to load') - ) + return ( + + {isLoaderRow ? ( + hasNextPage ? ( + t('Loading more...') ) : ( -
- {item.relatedType && ( - + t('Nothing more to load') + ) + ) : ( +
+ {data.relatedType && ( + + )} +
- {dayjs(item.createdAt).format('MM-DD HH:mm')} -
-
- {item.content} -
+ > + {dayjs(data.createdAt).format('MM-DD HH:mm')}
- )} - - ); - })} -
-
+
+ {data.content} +
+
+ )} + + ); + }} + />