22 * Original Code
33 * @link https://github.com/facebook/react-native/blob/main/packages/virtualized-lists/Lists/VirtualizedList.js
44 */
5-
65import type { NativeScrollEvent , NativeSyntheticEvent , VirtualizedListProps } from 'react-native' ;
7- import type { EnhancedScrollViewProps } from './types' ;
86import React , { ComponentType , useRef } from 'react' ;
97import { deferred } from './deferred' ;
8+ import type { EnhancedScrollViewProps } from './types' ;
109
1110type 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