Skip to content

Commit 4e423b3

Browse files
fixing inverted prop
1 parent 25e1fa8 commit 4e423b3

File tree

3 files changed

+105
-99
lines changed

3 files changed

+105
-99
lines changed

example/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const queryMoreMessages: (n: number, before: number | null, after: number
5353
// Lets resolve after 500 ms, to simulate network latency.
5454
setTimeout(() => {
5555
resolve(newMessages);
56-
}, 2000);
56+
}, 500);
5757
});
5858
};
5959

src/BidirectionalFlatList.tsx

Lines changed: 99 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-underscore-dangle */
22
import React, {
33
MutableRefObject,
4+
useCallback,
45
useEffect,
56
useImperativeHandle,
67
useRef,
@@ -16,7 +17,7 @@ import {
1617
} from 'react-native';
1718

1819
import { Virtuoso, VirtuosoHandle, VirtuosoProps } from 'react-virtuoso';
19-
import type { Props } from './types';
20+
import type { Props, WebFlatListProps } from './types';
2021

2122
const styles = StyleSheet.create({
2223
indicatorContainer: {
@@ -35,10 +36,8 @@ const styles = StyleSheet.create({
3536

3637
const waiter = () => new Promise((resolve) => setTimeout(resolve, 500));
3738
const dampingFactor = 5;
39+
const pullToRefreshReleaseThreshold = 100;
3840

39-
type WebFlatListProps<T> = Props<T> & {
40-
onScroll: (event: React.UIEvent<'div', UIEvent>) => void;
41-
};
4241
/**
4342
* Note:
4443
* - `onEndReached` and `onStartReached` must return a promise.
@@ -95,20 +94,39 @@ export const BidirectionalFlatList = (React.forwardRef(
9594
);
9695
const [onEndReachedInProgress, setOnEndReachedInProgress] = useState(false);
9796
const [vData, setVDate] = useState(data);
98-
const [firstItemIndex, setFirstItemIndex] = useState(10 ** 7);
97+
const firstItemIndex = useRef(0);
9998
const previousDataLength = useRef(data?.length || 0);
10099
const fadeAnimation = useRef(new Animated.Value(0)).current;
101100
const pan = useRef(new Animated.ValueXY()).current;
102101
const lastYValue = useRef(0);
103102
const panResponderActive = useRef(false);
103+
const onRefreshRef = useRef(onRefresh);
104+
const simulateRefreshAction = useRef(async () => {
105+
if (!scrollerRef.current) return;
106+
107+
prependingItems.current = true;
108+
await waiter();
109+
await onRefreshRef.current?.();
110+
scrollerRef.current.style.overflow = 'auto';
111+
112+
Animated.spring(pan, {
113+
toValue: { x: 0, y: 0 },
114+
useNativeDriver: true,
115+
}).start();
116+
117+
fadeAnimation.setValue(0);
118+
});
119+
104120
const resetPanHandlerAndScroller = () => {
105121
if (scrollerRef.current) {
106122
scrollerRef.current.style.overflow = 'auto';
107123
}
108124
panResponderActive.current = false;
109125
};
126+
110127
const startReached = async () => {
111128
if (
129+
!onStartReached ||
112130
prependingItems.current ||
113131
onStartReachedInProgress ||
114132
(inverted && !onEndReached) ||
@@ -119,6 +137,7 @@ export const BidirectionalFlatList = (React.forwardRef(
119137

120138
prependingItems.current = true;
121139
setOnStartReachedInProgress(true);
140+
122141
if (inverted) {
123142
await onEndReached?.();
124143
} else {
@@ -129,7 +148,9 @@ export const BidirectionalFlatList = (React.forwardRef(
129148

130149
const endReached = async () => {
131150
if (
151+
!onEndReached ||
132152
onEndReachedInProgress ||
153+
appendingItems.current ||
133154
(inverted && !onStartReached) ||
134155
(!inverted && !onEndReached)
135156
) {
@@ -179,15 +200,17 @@ export const BidirectionalFlatList = (React.forwardRef(
179200
}
180201

181202
panResponderActive.current = true;
182-
scrollerRef.current.style.overflow = 'hidden';
203+
// scrollerRef.current.style.overflow = 'hidden';
183204
pan.setValue({
184205
x: 0,
185206
y: lastYValue.current + gestureState.dy / dampingFactor,
186207
});
187208

188209
if (
189-
lastYValue.current + gestureState.dy / dampingFactor < 35 &&
190-
lastYValue.current + gestureState.dy / dampingFactor > -35
210+
lastYValue.current + gestureState.dy / dampingFactor <
211+
pullToRefreshReleaseThreshold &&
212+
lastYValue.current + gestureState.dy / dampingFactor >
213+
-pullToRefreshReleaseThreshold
191214
) {
192215
fadeAnimation.setValue(0);
193216
} else {
@@ -200,7 +223,8 @@ export const BidirectionalFlatList = (React.forwardRef(
200223
resetPanHandlerAndScroller();
201224

202225
if (
203-
lastYValue.current + gestureState.dy > 35 &&
226+
lastYValue.current + gestureState.dy >
227+
pullToRefreshReleaseThreshold &&
204228
offsetFromTop.current <= 0
205229
) {
206230
fadeAnimation.setValue(1);
@@ -221,52 +245,41 @@ export const BidirectionalFlatList = (React.forwardRef(
221245
})
222246
).current;
223247

224-
const Footer = () =>
225-
ListFooterComponent ? (
226-
<ListFooterComponent />
227-
) : onEndReachedInProgress ? (
228-
FooterLoadingIndicator ? (
229-
<FooterLoadingIndicator />
230-
) : showDefaultLoadingIndicators ? (
231-
<View style={styles.indicatorContainer}>
232-
<ActivityIndicator color={activityIndicatorColor} size={'small'} />
233-
</View>
234-
) : null
235-
) : null;
236-
237248
const handleScroll: VirtuosoProps<unknown>['onScroll'] = (e) => {
238249
const targetElem = e.target as HTMLDivElement;
239250
offsetFromBottom.current =
240251
targetElem.scrollHeight -
241252
(targetElem.scrollTop + targetElem.clientHeight);
242253
offsetFromTop.current = targetElem.scrollTop;
243254

244-
if (
245-
inverted &&
246-
!appendingItems.current &&
247-
!prependingItems.current &&
248-
startReached
249-
) {
250-
if (targetElem.scrollTop <= onEndReachedThreshold) {
251-
startReached();
252-
}
255+
if (appendingItems.current || prependingItems.current) {
256+
return;
257+
}
253258

254-
if (offsetFromBottom.current <= onStartReachedThreshold && endReached) {
255-
endReached();
256-
}
257-
} else {
258-
if (targetElem.scrollTop <= onStartReachedThreshold && endReached) {
259-
endReached();
260-
}
259+
if (targetElem.scrollTop <= onStartReachedThreshold) {
260+
startReached();
261+
}
261262

262-
if (offsetFromBottom.current <= onEndReachedThreshold && startReached) {
263-
startReached();
264-
}
263+
if (offsetFromBottom.current <= onEndReachedThreshold) {
264+
endReached();
265265
}
266266

267267
onScroll?.(e);
268268
};
269269

270+
const Footer = () =>
271+
ListFooterComponent ? (
272+
<ListFooterComponent />
273+
) : onEndReachedInProgress ? (
274+
FooterLoadingIndicator ? (
275+
<FooterLoadingIndicator />
276+
) : showDefaultLoadingIndicators ? (
277+
<View style={styles.indicatorContainer}>
278+
<ActivityIndicator color={activityIndicatorColor} size={'small'} />
279+
</View>
280+
) : null
281+
) : null;
282+
270283
const Header = () =>
271284
ListHeaderComponent ? (
272285
<ListHeaderComponent />
@@ -280,67 +293,60 @@ export const BidirectionalFlatList = (React.forwardRef(
280293
) : null
281294
) : null;
282295

283-
const itemContent = (index: number) => {
284-
if (!renderItem) {
285-
console.warn('Please specify renderItem prop');
286-
return null;
287-
}
288-
289-
if (!vData) {
290-
return null;
291-
}
292-
293-
const indexInData = inverted
294-
? vData.length - 1 - (index - firstItemIndex)
295-
: index - firstItemIndex;
296-
297-
return (
298-
<>
299-
{/* @ts-ignore */}
300-
{renderItem({ index, item: vData[indexInData] })}
301-
{indexInData !== vData.length && !!ItemSeparatorComponent && (
302-
<ItemSeparatorComponent />
303-
)}
304-
</>
305-
);
306-
};
307-
308-
const onRefreshRef = useRef(onRefresh);
309-
310-
const simulateRefreshAction = useRef(async () => {
311-
if (!scrollerRef.current || prependingItems.current) return;
296+
const itemContent = useCallback(
297+
(index: number) => {
298+
if (!renderItem) {
299+
console.warn('Please specify renderItem prop');
300+
return null;
301+
}
312302

313-
prependingItems.current = true;
314-
await waiter();
315-
await onRefreshRef.current?.();
316-
scrollerRef.current.style.overflow = 'auto';
303+
if (!vData) {
304+
return null;
305+
}
317306

318-
Animated.spring(pan, {
319-
toValue: { x: 0, y: 0 },
320-
useNativeDriver: true,
321-
}).start();
307+
const indexInData = inverted
308+
? vData.length - 1 - (index + Math.abs(firstItemIndex.current))
309+
: index + Math.abs(firstItemIndex.current);
322310

323-
fadeAnimation.setValue(0);
324-
});
311+
return (
312+
<>
313+
{renderItem({
314+
index,
315+
item: vData[indexInData],
316+
separators: {
317+
highlight: () => null,
318+
unhighlight: () => null,
319+
updateProps: () => null,
320+
},
321+
})}
322+
{indexInData !== vData.length && !!ItemSeparatorComponent && (
323+
<ItemSeparatorComponent />
324+
)}
325+
</>
326+
);
327+
},
328+
[vData?.length]
329+
);
325330

326331
useEffect(() => {
327332
const updateVirtuoso = () => {
328333
if (!data?.length) {
329334
return;
330335
}
331336

332-
let nextFirstItemIndex = firstItemIndex;
333-
if (prependingItems.current === true) {
337+
let nextFirstItemIndex = firstItemIndex.current;
338+
if (prependingItems.current) {
334339
nextFirstItemIndex =
335-
firstItemIndex - (data.length - previousDataLength.current);
336-
setFirstItemIndex(nextFirstItemIndex);
340+
firstItemIndex.current - (data.length - previousDataLength.current);
337341
}
338342

339-
setVDate(() => data);
340-
prependingItems.current = false;
341343
appendingItems.current = false;
344+
prependingItems.current = false;
342345
previousDataLength.current = data.length;
343346

347+
firstItemIndex.current = nextFirstItemIndex;
348+
setVDate(() => data);
349+
344350
if (
345351
enableAutoscrollToTop &&
346352
offsetFromBottom.current < autoscrollToTopThreshold
@@ -355,18 +361,19 @@ export const BidirectionalFlatList = (React.forwardRef(
355361
};
356362

357363
updateVirtuoso();
358-
}, [enableAutoscrollToTop, autoscrollToTopThreshold, data, firstItemIndex]);
364+
}, [enableAutoscrollToTop, autoscrollToTopThreshold, data, setVDate]);
359365

360366
useImperativeHandle(
361367
ref,
368+
// @ts-ignore
362369
() => ({
363370
flashScrollIndicators: () => null,
364371
getNativeScrollRef: () => null,
365372
getScrollableNode: () => null,
366373
getScrollResponder: () => null,
367374
recordInteraction: () => null,
368-
// @ts-ignore
369-
scrollToEnd: ({ animated }: { animated?: boolean | null }) => {
375+
scrollToEnd: (params = { animated: true }) => {
376+
const { animated } = params;
370377
if (!vData?.length) return;
371378

372379
if (inverted) {
@@ -432,7 +439,6 @@ export const BidirectionalFlatList = (React.forwardRef(
432439
}, [onRefresh]);
433440

434441
if (!vData?.length || vData?.length === 0) {
435-
// @ts-ignore
436442
return <ListEmptyComponent />;
437443
}
438444

@@ -443,8 +449,7 @@ export const BidirectionalFlatList = (React.forwardRef(
443449
Footer,
444450
Header,
445451
}}
446-
endReached={endReached}
447-
firstItemIndex={firstItemIndex}
452+
firstItemIndex={firstItemIndex.current}
448453
initialTopMostItemIndex={
449454
inverted
450455
? vData.length - 1 - initialScrollIndex
@@ -456,7 +461,6 @@ export const BidirectionalFlatList = (React.forwardRef(
456461
ref={virtuosoRef}
457462
// @ts-ignore
458463
scrollerRef={(ref) => (scrollerRef.current = ref)}
459-
startReached={startReached}
460464
totalCount={vData.length}
461465
/>
462466
);
@@ -488,8 +492,7 @@ export const BidirectionalFlatList = (React.forwardRef(
488492
Footer,
489493
Header,
490494
}}
491-
endReached={endReached}
492-
firstItemIndex={firstItemIndex}
495+
firstItemIndex={firstItemIndex.current}
493496
initialTopMostItemIndex={
494497
inverted
495498
? vData.length - 1 - initialScrollIndex
@@ -499,9 +502,7 @@ export const BidirectionalFlatList = (React.forwardRef(
499502
onScroll={handleScroll}
500503
overscan={300}
501504
ref={virtuosoRef}
502-
// @ts-ignore
503-
scrollerRef={(ref) => (scrollerRef.current = ref)}
504-
startReached={startReached}
505+
scrollerRef={(ref) => (scrollerRef.current = ref as HTMLElement)}
505506
totalCount={vData.length}
506507
/>
507508
</Animated.View>

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { FlatListProps } from 'react-native';
22

3+
export type WebFlatListProps<T> = Props<T> & {
4+
ListEmptyComponent: React.ComponentType;
5+
onScroll: (event: React.UIEvent<'div', UIEvent>) => void;
6+
};
7+
38
export type Props<T = unknown> = Omit<
49
FlatListProps<T>,
510
'maintainVisibleContentPosition'

0 commit comments

Comments
 (0)