From 01d81f39296b80899c4fcaadda8dffa3f3f28803 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sun, 14 Jul 2024 00:17:58 +0800 Subject: [PATCH] refactor: add dynamic virtual list --- src/client/components/DynamicVirtualList.tsx | 103 +++++++++++++++++++ src/client/components/feed/FeedEventItem.tsx | 38 +++---- src/client/routes/feed/$channelId/index.tsx | 4 +- 3 files changed, 125 insertions(+), 20 deletions(-) create mode 100644 src/client/components/DynamicVirtualList.tsx diff --git a/src/client/components/DynamicVirtualList.tsx b/src/client/components/DynamicVirtualList.tsx new file mode 100644 index 0000000..d7b0a4d --- /dev/null +++ b/src/client/components/DynamicVirtualList.tsx @@ -0,0 +1,103 @@ +import { useWatch } from '@/hooks/useWatch'; +import { useTranslation } from '@i18next-toolkit/react'; +import { 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: number; + renderItem: (item: T) => React.ReactElement; + renderEmpty?: () => React.ReactElement; +} +export const DynamicVirtualList: React.FC = React.memo( + (props) => { + const { + allData, + hasNextPage, + isFetchingNextPage, + onFetchNextPage, + estimateSize, + renderItem, + renderEmpty, + } = props; + + const parentRef = useRef(null); + const { t } = useTranslation(); + const rowVirtualizer = useVirtualizer({ + count: hasNextPage ? allData.length + 1 : allData.length, + getScrollElement: () => parentRef.current, + estimateSize: () => 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 && + (renderEmpty ? renderEmpty() : )} + +
+ {virtualItems.map((virtualRow) => { + const isLoaderRow = virtualRow.index > allData.length - 1; + const data = allData[virtualRow.index]; + + return ( +
+ {isLoaderRow + ? hasNextPage + ? t('Loading more...') + : t('Nothing more to load') + : renderItem(data)} +
+ ); + })} +
+
+
+ ); + } +); +DynamicVirtualList.displayName = 'DynamicVirtualList'; diff --git a/src/client/components/feed/FeedEventItem.tsx b/src/client/components/feed/FeedEventItem.tsx index 8edbc1b..1f30b23 100644 --- a/src/client/components/feed/FeedEventItem.tsx +++ b/src/client/components/feed/FeedEventItem.tsx @@ -17,12 +17,12 @@ export const FeedEventItem: React.FC<{ return (
-
-
+
+
@@ -31,25 +31,27 @@ export const FeedEventItem: React.FC<{
-
- {event.source} +
+
+ {event.source} - {event.eventName} + {event.eventName} - {event.tags.map((tag) => ( - {tag} - ))} + {event.tags.map((tag) => ( + {tag} + ))} +
+ + + +
{dayjs(event.createdAt).fromNow()}
+
+ +

{dayjs(event.createdAt).format('YYYY-MM-DD HH:mm:ss')}

+
+
- - - -
{dayjs(event.createdAt).fromNow()}
-
- -

{dayjs(event.createdAt).format('YYYY-MM-DD HH:mm:ss')}

-
-
); }); diff --git a/src/client/routes/feed/$channelId/index.tsx b/src/client/routes/feed/$channelId/index.tsx index 4b7b54d..ceeb1b2 100644 --- a/src/client/routes/feed/$channelId/index.tsx +++ b/src/client/routes/feed/$channelId/index.tsx @@ -15,7 +15,7 @@ import { FeedIntegration } from '@/components/feed/FeedIntegration'; import { DialogWrapper } from '@/components/DialogWrapper'; import { useSocketSubscribeList } from '@/api/socketio'; import { useMemo } from 'react'; -import { SimpleVirtualList } from '@/components/SimpleVirtualList'; +import { DynamicVirtualList } from '@/components/DynamicVirtualList'; export const Route = createFileRoute('/feed/$channelId/')({ beforeLoad: routeAuthBeforeLoad, @@ -112,7 +112,7 @@ function PageComponent() { } >
-