perf: optimize list rendering
This commit is contained in:
@@ -20,9 +20,9 @@ import PullToRefresh from 'react-simple-pull-to-refresh'
|
||||
import NoteCard from '../NoteCard'
|
||||
import PictureNoteCard from '../PictureNoteCard'
|
||||
|
||||
const NORMAL_RELAY_LIMIT = 100
|
||||
const ALGO_RELAY_LIMIT = 500
|
||||
const PICTURE_NOTE_LIMIT = 30
|
||||
const LIMIT = 100
|
||||
const ALGO_LIMIT = 500
|
||||
const SHOW_COUNT = 20
|
||||
|
||||
export default function NoteList({
|
||||
relayUrls,
|
||||
@@ -45,22 +45,17 @@ export default function NoteList({
|
||||
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
|
||||
const [events, setEvents] = useState<Event[]>([])
|
||||
const [newEvents, setNewEvents] = useState<Event[]>([])
|
||||
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
||||
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||
const [refreshing, setRefreshing] = useState(true)
|
||||
const [listMode, setListMode] = useState<TNoteListMode>(() => storage.getNoteListMode())
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||
const isPictures = useMemo(() => listMode === 'pictures', [listMode])
|
||||
const noteFilter = useMemo(() => {
|
||||
if (isPictures) {
|
||||
return {
|
||||
kinds: [PICTURE_EVENT_KIND],
|
||||
limit: PICTURE_NOTE_LIMIT,
|
||||
...filter
|
||||
}
|
||||
}
|
||||
return {
|
||||
kinds: [kinds.ShortTextNote, kinds.Repost, PICTURE_EVENT_KIND],
|
||||
limit: NORMAL_RELAY_LIMIT,
|
||||
kinds: isPictures
|
||||
? [PICTURE_EVENT_KIND]
|
||||
: [kinds.ShortTextNote, kinds.Repost, PICTURE_EVENT_KIND],
|
||||
...filter
|
||||
}
|
||||
}, [JSON.stringify(filter), isPictures])
|
||||
@@ -80,12 +75,11 @@ export default function NoteList({
|
||||
const relayInfos = await relayInfoService.getRelayInfos(relayUrls)
|
||||
areAlgoRelays = relayInfos.every((relayInfo) => checkAlgoRelay(relayInfo))
|
||||
}
|
||||
const filter = areAlgoRelays ? { ...noteFilter, limit: ALGO_RELAY_LIMIT } : noteFilter
|
||||
|
||||
let eventCount = 0
|
||||
const { closer, timelineKey } = await client.subscribeTimeline(
|
||||
[...relayUrls],
|
||||
filter,
|
||||
{ ...noteFilter, limit: areAlgoRelays ? ALGO_LIMIT : LIMIT },
|
||||
{
|
||||
onEvents: (events, eosed) => {
|
||||
if (eventCount > events.length) return
|
||||
@@ -124,23 +118,25 @@ export default function NoteList({
|
||||
}, [JSON.stringify(relayUrls), noteFilter, refreshCount])
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
if (!timelineKey || refreshing || !hasMore) return
|
||||
if (showCount < events.length) {
|
||||
setShowCount((prev) => prev + SHOW_COUNT)
|
||||
return
|
||||
}
|
||||
|
||||
if (!timelineKey || refreshing || !hasMore) return
|
||||
const newEvents = await client.loadMoreTimeline(
|
||||
timelineKey,
|
||||
events.length ? events[events.length - 1].created_at - 1 : dayjs().unix(),
|
||||
noteFilter.limit
|
||||
LIMIT
|
||||
)
|
||||
if (newEvents.length === 0) {
|
||||
setHasMore(false)
|
||||
return
|
||||
}
|
||||
setEvents((oldEvents) => [...oldEvents, ...newEvents])
|
||||
}, [timelineKey, refreshing, hasMore, events, noteFilter])
|
||||
}, [timelineKey, refreshing, hasMore, events, noteFilter, showCount])
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshing) return
|
||||
|
||||
const options = {
|
||||
root: null,
|
||||
rootMargin: '10px',
|
||||
@@ -164,7 +160,7 @@ export default function NoteList({
|
||||
observerInstance.unobserve(currentBottomRef)
|
||||
}
|
||||
}
|
||||
}, [refreshing, loadMore])
|
||||
}, [loadMore])
|
||||
|
||||
const showNewEvents = () => {
|
||||
setEvents((oldEvents) => [...newEvents, ...oldEvents])
|
||||
@@ -177,6 +173,7 @@ export default function NoteList({
|
||||
listMode={listMode}
|
||||
setListMode={(listMode) => {
|
||||
setListMode(listMode)
|
||||
setShowCount(SHOW_COUNT)
|
||||
topRef.current?.scrollIntoView({ behavior: 'instant', block: 'end' })
|
||||
storage.setNoteListMode(listMode)
|
||||
}}
|
||||
@@ -190,27 +187,29 @@ export default function NoteList({
|
||||
pullingContent=""
|
||||
>
|
||||
<div>
|
||||
{newEvents.filter((event: Event) => {
|
||||
return (
|
||||
(!filterMutedNotes || !mutePubkeys.includes(event.pubkey)) &&
|
||||
(listMode !== 'posts' || !isReplyNoteEvent(event))
|
||||
)
|
||||
}).length > 0 && (
|
||||
<div className="flex justify-center w-full my-2">
|
||||
<Button size="lg" onClick={showNewEvents}>
|
||||
{t('show new notes')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{events.length > 0 &&
|
||||
newEvents.filter((event: Event) => {
|
||||
return (
|
||||
(!filterMutedNotes || !mutePubkeys.includes(event.pubkey)) &&
|
||||
(listMode !== 'posts' || !isReplyNoteEvent(event))
|
||||
)
|
||||
}).length > 0 && (
|
||||
<div className="flex justify-center w-full my-2">
|
||||
<Button size="lg" onClick={showNewEvents}>
|
||||
{t('show new notes')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{isPictures ? (
|
||||
<PictureNoteCardMasonry
|
||||
className="px-2 sm:px-4 mt-2"
|
||||
columnCount={isLargeScreen ? 3 : 2}
|
||||
events={events}
|
||||
events={events.slice(0, showCount)}
|
||||
/>
|
||||
) : (
|
||||
<div>
|
||||
{events
|
||||
.slice(0, showCount)
|
||||
.filter((event: Event) => listMode !== 'posts' || !isReplyNoteEvent(event))
|
||||
.map((event) => (
|
||||
<NoteCard
|
||||
|
||||
@@ -26,6 +26,7 @@ import { FormattedTimestamp } from '../FormattedTimestamp'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
|
||||
const LIMIT = 100
|
||||
const SHOW_COUNT = 30
|
||||
|
||||
const NotificationList = forwardRef((_, ref) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -34,6 +35,7 @@ const NotificationList = forwardRef((_, ref) => {
|
||||
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
|
||||
const [refreshing, setRefreshing] = useState(true)
|
||||
const [notifications, setNotifications] = useState<Event[]>([])
|
||||
const [showCount, setShowCount] = useState(SHOW_COUNT)
|
||||
const [until, setUntil] = useState<number | undefined>(dayjs().unix())
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null)
|
||||
useImperativeHandle(
|
||||
@@ -70,15 +72,23 @@ const NotificationList = forwardRef((_, ref) => {
|
||||
onEvents: (events, eosed) => {
|
||||
if (eventCount > events.length) return
|
||||
eventCount = events.length
|
||||
setUntil(events.length >= LIMIT ? events[events.length - 1].created_at - 1 : undefined)
|
||||
setNotifications(events.filter((event) => event.pubkey !== pubkey))
|
||||
if (eosed) {
|
||||
setRefreshing(false)
|
||||
setUntil(events.length >= 0 ? events[events.length - 1].created_at - 1 : undefined)
|
||||
}
|
||||
},
|
||||
onNew: (event) => {
|
||||
if (event.pubkey === pubkey) return
|
||||
setNotifications((oldEvents) => [event, ...oldEvents])
|
||||
setNotifications((oldEvents) => {
|
||||
const index = oldEvents.findIndex(
|
||||
(oldEvent) => oldEvent.created_at < event.created_at
|
||||
)
|
||||
if (index === -1) {
|
||||
return [...oldEvents, event]
|
||||
}
|
||||
return [...oldEvents.slice(0, index), event, ...oldEvents.slice(index)]
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -93,26 +103,30 @@ const NotificationList = forwardRef((_, ref) => {
|
||||
}, [pubkey, refreshCount])
|
||||
|
||||
const loadMore = useCallback(async () => {
|
||||
if (showCount < notifications.length) {
|
||||
setShowCount((count) => count + SHOW_COUNT)
|
||||
return
|
||||
}
|
||||
|
||||
if (!pubkey || !timelineKey || !until || refreshing) return
|
||||
const notifications = await client.loadMoreTimeline(timelineKey, until, LIMIT)
|
||||
if (notifications.length === 0) {
|
||||
|
||||
const newNotifications = await client.loadMoreTimeline(timelineKey, until, LIMIT)
|
||||
if (newNotifications.length === 0) {
|
||||
setUntil(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (notifications.length > 0) {
|
||||
if (newNotifications.length > 0) {
|
||||
setNotifications((oldNotifications) => [
|
||||
...oldNotifications,
|
||||
...notifications.filter((event) => event.pubkey !== pubkey)
|
||||
...newNotifications.filter((event) => event.pubkey !== pubkey)
|
||||
])
|
||||
}
|
||||
|
||||
setUntil(notifications[notifications.length - 1].created_at - 1)
|
||||
}, [pubkey, timelineKey, until, refreshing])
|
||||
setUntil(newNotifications[newNotifications.length - 1].created_at - 1)
|
||||
}, [pubkey, timelineKey, until, refreshing, showCount, notifications])
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshing) return
|
||||
|
||||
const options = {
|
||||
root: null,
|
||||
rootMargin: '10px',
|
||||
@@ -136,7 +150,7 @@ const NotificationList = forwardRef((_, ref) => {
|
||||
observerInstance.unobserve(currentBottomRef)
|
||||
}
|
||||
}
|
||||
}, [refreshing, loadMore])
|
||||
}, [loadMore])
|
||||
|
||||
return (
|
||||
<PullToRefresh
|
||||
@@ -147,7 +161,7 @@ const NotificationList = forwardRef((_, ref) => {
|
||||
pullingContent=""
|
||||
>
|
||||
<div>
|
||||
{notifications.map((notification) => (
|
||||
{notifications.slice(0, showCount).map((notification) => (
|
||||
<NotificationItem key={notification.id} notification={notification} />
|
||||
))}
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
|
||||
@@ -320,7 +320,8 @@ class ClientService extends EventTarget {
|
||||
timeline.refs = newRefs.concat(timeline.refs)
|
||||
onEvents(newEvents.concat(cachedEvents), true)
|
||||
}
|
||||
}
|
||||
},
|
||||
eoseTimeout: 10000 // 10s
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user