Skip to content
This repository was archived by the owner on Aug 13, 2024. It is now read-only.

Commit e51fa82

Browse files
committed
fix: modified onEdgeReached handlers to be executed sequentially
1 parent c02f952 commit e51fa82

File tree

1 file changed

+42
-43
lines changed

1 file changed

+42
-43
lines changed

src/useBiDirectional.tsx

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
* Original Code
33
* @link https://github.com/facebook/react-native/blob/main/packages/virtualized-lists/Lists/VirtualizedList.js
44
*/
5-
65
import type { NativeScrollEvent, NativeSyntheticEvent, VirtualizedListProps } from 'react-native';
7-
import type { EnhancedScrollViewProps } from './types';
86
import React, { ComponentType, useRef } from 'react';
97
import { deferred } from './deferred';
8+
import type { EnhancedScrollViewProps } from './types';
109

1110
type ScrollMetrics = {
1211
offset: number;
@@ -48,9 +47,11 @@ export function useBiDirectional<P extends Partial<VirtualizedListProps<any> & E
4847
const scrollRef = () => ref || innerRef;
4948
const scrollMetrics = useRef<ScrollMetrics>({ offset: 0, visibleLength: -1, contentLength: -1, timestamp: 0 });
5049

50+
const inProgressCall = useRef<Promise<void>>(Promise.resolve());
51+
5152
const isHorizontal = props.horizontal ?? false;
52-
const sentEndForContentLength = useRef(0);
53-
const sentStartForContentLength = useRef(0);
53+
const sentEndForContentLength = useRef(new Map<number, boolean>());
54+
const sentStartForContentLength = useRef(new Map<number, boolean>());
5455

5556
function updateScrollMetrics(metrics: Partial<ScrollMetrics>) {
5657
if (typeof metrics.offset === 'number') {
@@ -70,12 +71,14 @@ export function useBiDirectional<P extends Partial<VirtualizedListProps<any> & E
7071
async function lazyOnEndReached(distanceFromEnd: number): Promise<void> {
7172
const p = deferred<void>();
7273

73-
const res = props.onEndReached?.({ distanceFromEnd });
74+
const response = props.onEndReached?.({ distanceFromEnd });
75+
const resolveLazily = () => setTimeout(() => p.resolve(), DEFAULT_LAZY_FETCH_MS);
76+
7477
// @ts-ignore
75-
if (res instanceof Promise) {
76-
res.then(() => p.resolve());
78+
if (response instanceof Promise) {
79+
response.then(resolveLazily).catch(p.reject);
7780
} else {
78-
setTimeout(() => p.resolve(), DEFAULT_LAZY_FETCH_MS);
81+
resolveLazily();
7982
}
8083

8184
return p.promise;
@@ -84,12 +87,14 @@ export function useBiDirectional<P extends Partial<VirtualizedListProps<any> & E
8487
async function lazyOnStartReached(distanceFromStart: number): Promise<void> {
8588
const p = deferred<void>();
8689

87-
const res = props.onStartReached?.({ distanceFromStart });
90+
const response = props.onStartReached?.({ distanceFromStart });
91+
const resolveLazily = () => setTimeout(() => p.resolve(), DEFAULT_LAZY_FETCH_MS);
92+
8893
// @ts-ignore
89-
if (res instanceof Promise) {
90-
res.then(() => p.resolve());
94+
if (response instanceof Promise) {
95+
response.then(resolveLazily).catch(p.reject);
9196
} else {
92-
setTimeout(() => p.resolve(), DEFAULT_LAZY_FETCH_MS);
97+
resolveLazily();
9398
}
9499

95100
return p.promise;
@@ -111,51 +116,45 @@ export function useBiDirectional<P extends Partial<VirtualizedListProps<any> & E
111116
// First check if the user just scrolled within the end threshold
112117
// and call onEndReached only once for a given content length,
113118
// and only if onStartReached is not being executed
114-
if (onEndReached && isWithinEndThreshold && sentEndForContentLength.current !== contentLength) {
115-
sentEndForContentLength.current = contentLength;
116-
117-
lazyOnEndReached(distanceFromEnd).then(() => {
118-
maybeRecallOnEdgeReached('distanceFromEnd', endThreshold);
119-
});
119+
if (onEndReached && isWithinEndThreshold && !sentEndForContentLength.current.has(contentLength)) {
120+
sentEndForContentLength.current.set(contentLength, true);
121+
inProgressCall.current = inProgressCall.current
122+
.catch(() => {
123+
sentEndForContentLength.current.delete(contentLength);
124+
})
125+
.finally(() => lazyOnEndReached(distanceFromEnd));
120126
}
121127
// Next check if the user just scrolled within the start threshold
122128
// and call onStartReached only once for a given content length,
123129
// and only if onEndReached is not being executed
124-
else if (onStartReached != null && isWithinStartThreshold && sentStartForContentLength.current !== contentLength) {
130+
else if (
131+
onStartReached != null &&
132+
isWithinStartThreshold &&
133+
!sentStartForContentLength.current.has(contentLength)
134+
) {
125135
// On initial mount when using initialScrollIndex the offset will be 0 initially
126136
// and will trigger an unexpected onStartReached. To avoid this we can use
127137
// timestamp to differentiate between the initial scroll metrics and when we actually
128138
// received the first scroll event.
129139
if (!props.initialScrollIndex || scrollMetrics.current.timestamp !== 0) {
130-
sentStartForContentLength.current = contentLength;
131-
132-
lazyOnStartReached(distanceFromStart).then(() => {
133-
maybeRecallOnEdgeReached('distanceFromStart', endThreshold);
134-
});
140+
sentStartForContentLength.current.set(contentLength, true);
141+
inProgressCall.current = inProgressCall.current
142+
.catch(() => {
143+
sentStartForContentLength.current.delete(contentLength);
144+
})
145+
.finally(() => lazyOnStartReached(distanceFromEnd));
135146
}
136147
}
148+
// NOTE: Changed to Map to handle multiple requests on the same content length
137149
// If the user scrolls away from the start or end and back again,
138150
// cause onStartReached or onEndReached to be triggered again
139151
else {
140-
if (!isWithinEndThreshold) {
141-
sentEndForContentLength.current = 0;
142-
}
143-
if (!isWithinStartThreshold) {
144-
sentStartForContentLength.current = 0;
145-
}
146-
}
147-
}
148-
149-
function maybeRecallOnEdgeReached(key: 'distanceFromStart' | 'distanceFromEnd', threshold: number) {
150-
const distanceFromEdge = getDistanceFrom(
151-
scrollMetrics.current.offset,
152-
scrollMetrics.current.visibleLength,
153-
scrollMetrics.current.contentLength
154-
)[key];
155-
156-
if (distanceFromEdge <= threshold) {
157-
const lazyOnEdgeReached = key === 'distanceFromStart' ? lazyOnStartReached : lazyOnEndReached;
158-
lazyOnEdgeReached(distanceFromEdge);
152+
// if (!isWithinEndThreshold) {
153+
// sentEndForContentLength.current = 0;
154+
// }
155+
// if (!isWithinStartThreshold) {
156+
// sentStartForContentLength.current = 0;
157+
// }
159158
}
160159
}
161160

0 commit comments

Comments
 (0)