Skip to content

Commit cdd5e62

Browse files
committed
Add support for SectionList
1 parent 4fd8789 commit cdd5e62

File tree

3 files changed

+141
-0
lines changed

3 files changed

+141
-0
lines changed

src/SectionList.android.tsx

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import React, { MutableRefObject, useEffect, useRef } from 'react';
2+
import {
3+
DefaultSectionT,
4+
NativeModules,
5+
Platform,
6+
SectionList as RNSectionList,
7+
SectionListProps as RNSectionListProps,
8+
} from 'react-native';
9+
10+
export const ScrollViewManager = NativeModules.MvcpScrollViewManager;
11+
12+
export default React.forwardRef(
13+
<ItemT extends any, SectionT = DefaultSectionT>(
14+
props: RNSectionListProps<ItemT, SectionT>,
15+
forwardedRef:
16+
| ((instance: RNSectionList<ItemT, SectionT> | null) => void)
17+
| MutableRefObject<RNSectionList<ItemT, SectionT> | null>
18+
| null
19+
) => {
20+
const sectionListRef = useRef<RNSectionList<ItemT, SectionT> | null>(null);
21+
const isMvcpEnabledNative = useRef<boolean>(false);
22+
const handle = useRef<number | null>(null);
23+
const enableMvcpRetriesCount = useRef<number>(0);
24+
const isMvcpPropPresentRef = useRef(!!props.maintainVisibleContentPosition);
25+
26+
const autoscrollToTopThreshold = useRef<number | null>(
27+
props.maintainVisibleContentPosition?.autoscrollToTopThreshold ||
28+
-Number.MAX_SAFE_INTEGER
29+
);
30+
const minIndexForVisible = useRef<number>(
31+
props.maintainVisibleContentPosition?.minIndexForVisible || 1
32+
);
33+
const retryTimeoutId = useRef<NodeJS.Timeout>();
34+
const debounceTimeoutId = useRef<NodeJS.Timeout>();
35+
const disableMvcpRef = useRef(async () => {
36+
isMvcpEnabledNative.current = false;
37+
if (!handle?.current) {
38+
return;
39+
}
40+
await ScrollViewManager.disableMaintainVisibleContentPosition(
41+
handle.current
42+
);
43+
});
44+
const enableMvcpWithRetriesRef = useRef(() => {
45+
// debounce to wait till consecutive mvcp enabling
46+
// this ensures that always previous handles are disabled first
47+
if (debounceTimeoutId.current) {
48+
clearTimeout(debounceTimeoutId.current);
49+
}
50+
debounceTimeoutId.current = setTimeout(async () => {
51+
// disable any previous enabled handles
52+
await disableMvcpRef.current();
53+
54+
if (
55+
!sectionListRef.current ||
56+
!isMvcpPropPresentRef.current ||
57+
isMvcpEnabledNative.current ||
58+
Platform.OS !== 'android'
59+
) {
60+
return;
61+
}
62+
const scrollableNode = sectionListRef.current.getScrollableNode();
63+
64+
try {
65+
handle.current = await ScrollViewManager.enableMaintainVisibleContentPosition(
66+
scrollableNode,
67+
autoscrollToTopThreshold.current,
68+
minIndexForVisible.current
69+
);
70+
} catch (error: any) {
71+
/**
72+
* enableMaintainVisibleContentPosition from native module may throw IllegalViewOperationException,
73+
* in case view is not ready yet. In that case, lets do a retry!! (max of 10 tries)
74+
*/
75+
if (enableMvcpRetriesCount.current < 10) {
76+
retryTimeoutId.current = setTimeout(
77+
enableMvcpWithRetriesRef.current,
78+
100
79+
);
80+
enableMvcpRetriesCount.current += 1;
81+
}
82+
}
83+
}, 300);
84+
});
85+
86+
useEffect(() => {
87+
// when the mvcp prop changes
88+
// enable natively again, if the prop has changed
89+
const propAutoscrollToTopThreshold =
90+
props.maintainVisibleContentPosition?.autoscrollToTopThreshold ||
91+
-Number.MAX_SAFE_INTEGER;
92+
const propMinIndexForVisible =
93+
props.maintainVisibleContentPosition?.minIndexForVisible || 1;
94+
const hasMvcpChanged =
95+
autoscrollToTopThreshold.current !== propAutoscrollToTopThreshold ||
96+
minIndexForVisible.current !== propMinIndexForVisible ||
97+
isMvcpPropPresentRef.current !== !!props.maintainVisibleContentPosition;
98+
99+
if (hasMvcpChanged) {
100+
enableMvcpRetriesCount.current = 0;
101+
autoscrollToTopThreshold.current = propAutoscrollToTopThreshold;
102+
minIndexForVisible.current = propMinIndexForVisible;
103+
isMvcpPropPresentRef.current = !!props.maintainVisibleContentPosition;
104+
enableMvcpWithRetriesRef.current();
105+
}
106+
}, [props.maintainVisibleContentPosition]);
107+
108+
const refCallback = useRef<
109+
(instance: RNSectionList<ItemT, SectionT>) => void
110+
>((ref) => {
111+
sectionListRef.current = ref;
112+
enableMvcpWithRetriesRef.current();
113+
if (typeof forwardedRef === 'function') {
114+
forwardedRef(ref);
115+
} else if (forwardedRef) {
116+
forwardedRef.current = ref;
117+
}
118+
}).current;
119+
120+
useEffect(() => {
121+
const disableMvcp = disableMvcpRef.current;
122+
return () => {
123+
// clean up the retry mechanism
124+
if (debounceTimeoutId.current) {
125+
clearTimeout(debounceTimeoutId.current);
126+
}
127+
// clean up any debounce
128+
if (debounceTimeoutId.current) {
129+
clearTimeout(debounceTimeoutId.current);
130+
}
131+
disableMvcp();
132+
};
133+
}, []);
134+
135+
return <RNSectionList<ItemT, SectionT> ref={refCallback} {...props} />;
136+
}
137+
);

src/SectionList.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { SectionList } from 'react-native';
2+
3+
export default SectionList;

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as FlatList } from './FlatList';
22
export { default as ScrollView } from './ScrollView';
3+
export { default as SectionList } from './SectionList';

0 commit comments

Comments
 (0)