From 02121152b3e3cdbbaaff8a34b51cb8e9b2010538 Mon Sep 17 00:00:00 2001 From: Rajarshee Chatterjee Date: Fri, 6 Feb 2026 15:40:14 +0530 Subject: [PATCH] fix: Stabilize Scroll Handler References to Prevent Infinite Re-render Loop (Closes #1679) Co-Authored-By: Claude Opus 4.6 --- src/screens/browse/discover/MalTopNovels.tsx | 28 +++++++++---- .../novel/components/NovelScreenList.tsx | 39 ++++++++++--------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/screens/browse/discover/MalTopNovels.tsx b/src/screens/browse/discover/MalTopNovels.tsx index ae934d30..1b85ff49 100644 --- a/src/screens/browse/discover/MalTopNovels.tsx +++ b/src/screens/browse/discover/MalTopNovels.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { StyleSheet, View, @@ -97,6 +97,25 @@ const BrowseMalScreen = ({ navigation }: BrowseMalScreenProps) => { ); }; + const loadingMore = useRef(false); + + const onScroll = useCallback( + ({ nativeEvent }: { nativeEvent: NativeScrollEvent }) => { + if (!searchText && !loadingMore.current && isCloseToBottom(nativeEvent)) { + loadingMore.current = true; + + setLimit(before => { + const newLimit = before + 50; + getNovels(newLimit).finally(() => { + loadingMore.current = false; + }); + return newLimit; + }); + } + }, + [searchText, getNovels], + ); + const ListEmptyComponent = useCallback( () => ( { keyExtractor={(item, index) => item.novelName + index} renderItem={renderItem} ListEmptyComponent={ListEmptyComponent} - onScroll={({ nativeEvent }) => { - if (!searchText && isCloseToBottom(nativeEvent)) { - getNovels(limit + 50); - setLimit(before => before + 50); - } - }} + onScroll={onScroll} ListFooterComponent={ !searchText ? ( diff --git a/src/screens/novel/components/NovelScreenList.tsx b/src/screens/novel/components/NovelScreenList.tsx index 992bd5a4..25b2e181 100644 --- a/src/screens/novel/components/NovelScreenList.tsx +++ b/src/screens/novel/components/NovelScreenList.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import ChapterItem from './ChapterItem'; import NovelInfoHeader from './Info/NovelInfoHeader'; -import { useRef, useState } from 'react'; +import { useCallback, useMemo, useRef, useState } from 'react'; import { pickCustomNovelCover } from '@database/queries/NovelQueries'; import { ChapterInfo, NovelInfo } from '@database/types'; import { useBoolean } from '@hooks/index'; @@ -119,18 +119,26 @@ const NovelScreenList = ({ const deleteDownloadsSnackbar = useBoolean(); - const onPageScroll = (event: NativeSyntheticEvent) => { - const y = event.nativeEvent.contentOffset.y; + const extraData = useMemo( + () => [chapters.length, selected.length, novel.id, loading, downloadQueue.length], + [chapters.length, selected.length, novel.id, loading, downloadQueue.length], + ); - headerOpacity.set(y < 50 ? 0 : (y - 50) / 150); - const currentScrollPosition = Math.floor(y) ?? 0; - if (useFabForContinueReading && lastRead) { - setIsFabExtended(currentScrollPosition <= 0); - } + const onPageScroll = useCallback( + (event: NativeSyntheticEvent) => { + const y = event.nativeEvent.contentOffset.y; - const screenHeight = Dimensions.get('window').height; - setShowScrollToTop(currentScrollPosition > screenHeight / 2); - }; + headerOpacity.set(y < 50 ? 0 : (y - 50) / 150); + const currentScrollPosition = Math.floor(y) ?? 0; + if (useFabForContinueReading && lastRead) { + setIsFabExtended(currentScrollPosition <= 0); + } + + const screenHeight = Dimensions.get('window').height; + setShowScrollToTop(currentScrollPosition > screenHeight / 2); + }, + [headerOpacity, useFabForContinueReading, lastRead], + ); const onRefresh = async () => { if (novel.id !== 'NO_ID') { @@ -318,13 +326,7 @@ const NovelScreenList = ({ estimatedItemSize={64} data={chapters} recycleItems - extraData={[ - chapters.length, - selected.length, - novel.id, - loading, - downloadQueue.length, - ]} + extraData={extraData} // ListEmptyComponent={ListEmptyComponent} ListFooterComponent={ !fetching ? ( @@ -376,6 +378,7 @@ const NovelScreenList = ({ onEndReached={getNextChapterBatch} onEndReachedThreshold={6} onScroll={onPageScroll} + scrollEventThrottle={16} drawDistance={1000} ListHeaderComponent={