Skip to content

Commit 9b3c072

Browse files
authored
Merge pull request #1750 from wix/infra/InfiniteList
Convert HorizontalList to InfiniteList
2 parents 310f1dc + 494dd9e commit 9b3c072

File tree

4 files changed

+131
-89
lines changed

4 files changed

+131
-89
lines changed

src/infinite-list/index.tsx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// TODO: Make this a common component for all horizontal lists in this lib
2+
import React, {forwardRef, useCallback, useMemo, useRef} from 'react';
3+
import {ScrollViewProps} from 'react-native';
4+
import {DataProvider, LayoutProvider, RecyclerListView, RecyclerListViewProps} from 'recyclerlistview';
5+
import inRange from 'lodash/inRange';
6+
7+
import constants from '../commons/constants';
8+
9+
const dataProviderMaker = (items: string[]) => new DataProvider((item1, item2) => item1 !== item2).cloneWithRows(items);
10+
11+
export interface InfiniteListProps
12+
extends Omit<RecyclerListViewProps, 'dataProvider' | 'layoutProvider' | 'rowRenderer'> {
13+
data: any[];
14+
renderItem: RecyclerListViewProps['rowRenderer'];
15+
pageWidth?: number;
16+
pageHeight?: number;
17+
onPageChange?: (pageIndex: number, prevPageIndex: number) => void;
18+
onReachEdge?: (pageIndex: number) => void;
19+
onReachNearEdge?: (pageIndex: number) => void;
20+
onReachNearEdgeThreshold?: number;
21+
initialPageIndex?: number;
22+
scrollViewProps?: ScrollViewProps;
23+
}
24+
25+
const InfiniteList = (props: InfiniteListProps, ref: any) => {
26+
const {
27+
renderItem,
28+
data,
29+
pageWidth = constants.screenWidth,
30+
pageHeight = constants.screenHeight,
31+
onPageChange,
32+
onReachEdge,
33+
onReachNearEdge,
34+
onReachNearEdgeThreshold,
35+
initialPageIndex = 0,
36+
extendedState,
37+
scrollViewProps
38+
} = props;
39+
const dataProvider = useMemo(() => {
40+
return dataProviderMaker(data);
41+
}, [data]);
42+
43+
const layoutProvider = useRef(
44+
new LayoutProvider(
45+
() => 'page',
46+
(_type, dim) => {
47+
dim.width = pageWidth;
48+
dim.height = pageHeight;
49+
}
50+
)
51+
);
52+
53+
const pageIndex = useRef<number>();
54+
const isOnEdge = useRef(false);
55+
const isNearEdge = useRef(false);
56+
57+
const onScroll = useCallback(
58+
(event, offsetX, offsetY) => {
59+
const newPageIndex = Math.round(event.nativeEvent.contentOffset.x / pageWidth);
60+
61+
if (pageIndex.current !== newPageIndex) {
62+
if (pageIndex.current !== undefined) {
63+
onPageChange?.(newPageIndex, pageIndex.current);
64+
65+
isOnEdge.current = false;
66+
isNearEdge.current = false;
67+
68+
if (newPageIndex === 0 || newPageIndex === data.length - 1) {
69+
isOnEdge.current = true;
70+
} else if (onReachNearEdgeThreshold && !inRange(newPageIndex, onReachNearEdgeThreshold, data.length - onReachNearEdgeThreshold)) {
71+
isNearEdge.current = true;
72+
}
73+
}
74+
pageIndex.current = newPageIndex;
75+
}
76+
77+
props.onScroll?.(event, offsetX, offsetY);
78+
},
79+
[props.onScroll, onPageChange, data.length]
80+
);
81+
82+
const onMomentumScrollEnd = useCallback(
83+
event => {
84+
if (isOnEdge.current) {
85+
onReachEdge?.(pageIndex.current!);
86+
} else if (isNearEdge.current) {
87+
onReachNearEdge?.(pageIndex.current!);
88+
}
89+
90+
scrollViewProps?.onMomentumScrollEnd?.(event);
91+
},
92+
[scrollViewProps?.onMomentumScrollEnd, onReachEdge, onReachNearEdge]
93+
);
94+
95+
const style = useMemo(() => {
96+
return {height: pageHeight};
97+
}, [pageHeight]);
98+
99+
return (
100+
<RecyclerListView
101+
ref={ref}
102+
isHorizontal
103+
rowRenderer={renderItem}
104+
dataProvider={dataProvider}
105+
layoutProvider={layoutProvider.current}
106+
extendedState={extendedState}
107+
initialRenderIndex={initialPageIndex}
108+
renderAheadOffset={5 * pageWidth}
109+
onScroll={onScroll}
110+
style={style}
111+
scrollViewProps={{
112+
pagingEnabled: true,
113+
bounces: false,
114+
...scrollViewProps,
115+
onMomentumScrollEnd
116+
}}
117+
/>
118+
);
119+
};
120+
121+
export default forwardRef(InfiniteList);

src/timeline-list/HorizontalList.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/timeline-list/index.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import React, {useCallback, useContext, useEffect, useRef, useState} from 'react';
22
// import {Text} from 'react-native';
3-
import identity from 'lodash/identity';
43
import throttle from 'lodash/throttle';
54

65
import Context from '../expandableCalendar/Context';
76
import {UpdateSources} from '../expandableCalendar/commons';
87
import Timeline, {TimelineProps} from '../timeline/Timeline';
9-
import HorizontalList from './HorizontalList';
10-
import useTimelinePages, {INITIAL_PAGE} from './useTimelinePages';
8+
import InfiniteList from '../infinite-list';
9+
import useTimelinePages, {INITIAL_PAGE, NEAR_EDGE_THRESHOLD} from './useTimelinePages';
1110

1211
export interface TimelineListProps {
1312
events: {[date: string]: TimelineProps['events']};
@@ -29,7 +28,6 @@ const TimelineList = (props: TimelineListProps) => {
2928
scrollToPageDebounce,
3029
shouldResetPages,
3130
isOutOfRange,
32-
isNearEdges
3331
} = useTimelinePages({date, listRef});
3432

3533
useEffect(() => {
@@ -65,15 +63,15 @@ const TimelineList = (props: TimelineListProps) => {
6563
const newDate = pagesRef.current[pageIndex];
6664
if (newDate !== prevDate.current) {
6765
setDate(newDate, UpdateSources.LIST_DRAG);
68-
69-
if (isNearEdges(pageIndex)) {
70-
shouldResetPages.current = isNearEdges(pageIndex);
71-
}
7266
}
7367
}, 0),
7468
[]
7569
);
7670

71+
const onReachNearEdge = useCallback(() => {
72+
shouldResetPages.current = true;
73+
}, []);
74+
7775
const onTimelineOffsetChange = useCallback(offset => {
7876
setTimelineOffset(offset);
7977
}, []);
@@ -104,16 +102,17 @@ const TimelineList = (props: TimelineListProps) => {
104102
);
105103

106104
return (
107-
<HorizontalList
105+
<InfiniteList
108106
ref={listRef}
109107
data={pages}
110108
renderItem={renderPage}
111109
onPageChange={onPageChange}
110+
onReachNearEdge={onReachNearEdge}
111+
onReachNearEdgeThreshold={NEAR_EDGE_THRESHOLD}
112112
onScroll={onScroll}
113113
extendedState={{todayEvents: events[date], pages}}
114114
initialPageIndex={INITIAL_PAGE}
115115
scrollViewProps={{
116-
keyExtractor: identity,
117116
onMomentumScrollEnd
118117
}}
119118
/>

src/timeline-list/useTimelinePages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import constants from '../commons/constants';
88
import {generateDay} from '../dateutils';
99

1010
const PAGES_COUNT = 100;
11-
const NEAR_EDGE_THRESHOLD = 10;
11+
export const NEAR_EDGE_THRESHOLD = 10;
1212
export const INITIAL_PAGE = Math.floor(PAGES_COUNT / 2);
1313

1414

0 commit comments

Comments
 (0)