fix: Stabilize Scroll Handler References to Prevent Infinite Re-render Loop (Closes #1679)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rajarshee Chatterjee
2026-02-06 15:40:14 +05:30
parent caed965b6b
commit 02121152b3
2 changed files with 42 additions and 25 deletions
+21 -7
View File
@@ -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(
() => (
<ErrorView
@@ -145,12 +164,7 @@ const BrowseMalScreen = ({ navigation }: BrowseMalScreenProps) => {
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 ? (
<View style={styles.paddingVertical}>
@@ -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<NativeScrollEvent>) => {
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<NativeScrollEvent>) => {
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={
<NovelInfoHeader