Skip to content

Commit d38dbd7

Browse files
committedApr 18, 2023
feat: added pull to refresh for FlatList, VirtualizedList and SectionList
1 parent d6a167b commit d38dbd7

11 files changed

+252
-10
lines changed
 

‎example/.storybook/stories/FlatList.stories.js

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FlatListPaginationExample } from '../../examples/FlatListPaginationExample';
22
import { AdvancedFlatListPaginationExample } from '../../examples/AdvancedFlatListPaginationExample';
3+
import { FlatListPullToRefreshExample } from '../../examples/FlatListPullToRefreshExample';
34

45
const FlatListMeta = {
56
title: 'FlatList',
@@ -13,3 +14,7 @@ SimplePagination.storyName = 'Basic Pagination'
1314

1415
export const AdvancedPagination = ()=> <AdvancedFlatListPaginationExample />;
1516
AdvancedFlatListPaginationExample.storyName = 'Advanced Pagination'
17+
18+
19+
export const AdvancedPullToRefresh = ()=> <FlatListPullToRefreshExample />;
20+
AdvancedPullToRefresh.storyName = 'Pull to refresh'

‎example/.storybook/stories/SectionList.stories.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { SectionListPaginationExample } from '../../examples/SectionListPaginationExample';
3+
import { SectionListPullToRefreshExample } from '../../examples/SectionListPullToRefreshExample';
34

45
const SectionListMeta = {
56
title: 'SectionList',
@@ -11,3 +12,6 @@ export default SectionListMeta;
1112
export const Pagination = {
1213
component: SectionListPaginationExample,
1314
};
15+
16+
export const AdvancedPullToRefresh = ()=> <SectionListPullToRefreshExample />;
17+
AdvancedPullToRefresh.storyName = 'Pull to refresh'

‎example/.storybook/stories/VirtualizedList.stories.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { VirtualizedListPaginationExample } from '../../examples/VirtualizedListPaginationExample';
3+
import { VirtualizedListPullToRefreshExample } from '../../examples/VirtualizedListPullToRefreshExample';
34

45
const VirtualizedListMeta = {
56
title: 'VirtualizedList',
@@ -11,3 +12,6 @@ export default VirtualizedListMeta;
1112
export const Pagination = {
1213
component: VirtualizedListPaginationExample,
1314
};
15+
16+
export const AdvancedPullToRefresh = ()=> <VirtualizedListPullToRefreshExample />;
17+
AdvancedPullToRefresh.storyName = 'Pull to refresh'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { ListItem } from 'example/src/components';
2+
import { generateRandomColor } from 'example/src/utils/colors';
3+
import React, { useRef, useState } from 'react';
4+
import { FlatList, StyleSheet, Text } from 'react-native';
5+
import { useList } from 'react-native-use-list';
6+
7+
const list1 = generateRandomColor(5);
8+
const list2 = generateRandomColor(10);
9+
10+
export const FlatListPullToRefreshExample = () => {
11+
const [data, setData] = useState(list1);
12+
13+
const listRef = useRef(null);
14+
15+
const updateData = () => {
16+
return new Promise((resolve) => {
17+
setTimeout(() => {
18+
setData(list2);
19+
resolve(true);
20+
}, 1000 * 2);
21+
});
22+
};
23+
24+
const { isRefreshing, refreshController } = useList(listRef, {
25+
onRefresh: updateData,
26+
});
27+
28+
return (
29+
<>
30+
<Text
31+
style={styles.title}
32+
>{`isRefreshing: ${isRefreshing} - Length: ${data.length}`}</Text>
33+
<FlatList
34+
ref={listRef}
35+
data={data}
36+
renderItem={({ item: color }) => (
37+
<ListItem text={color} style={{ backgroundColor: color }} />
38+
)}
39+
{...refreshController}
40+
/>
41+
</>
42+
);
43+
};
44+
const styles = StyleSheet.create({
45+
title: {
46+
fontSize: 24,
47+
textAlign: 'center',
48+
marginVertical: 8,
49+
},
50+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React, { useRef, useState } from 'react';
2+
import { SectionList, StyleSheet, Text } from 'react-native';
3+
import { ListItem } from '../src/components';
4+
import { useList } from 'react-native-use-list';
5+
6+
const DATA = [
7+
{
8+
title: 'Main dishes',
9+
data: ['Pizza', 'Burger', 'Risotto'],
10+
},
11+
{
12+
title: 'Sides',
13+
data: ['French Fries', 'Onion Rings', 'Fried Shrimps'],
14+
},
15+
{
16+
title: 'Drinks',
17+
data: ['Water', 'Coke', 'Beer'],
18+
},
19+
{
20+
title: 'Desserts',
21+
data: ['Cheese Cake', 'Ice Cream'],
22+
},
23+
];
24+
25+
export const SectionListPullToRefreshExample = () => {
26+
const [data, setData] = useState(DATA.slice(0, 2));
27+
28+
const listRef = useRef(null);
29+
30+
const updateData = () => {
31+
return new Promise((resolve) => {
32+
setTimeout(() => {
33+
setData(DATA);
34+
resolve(true);
35+
}, 1000 * 2);
36+
});
37+
};
38+
39+
const { isRefreshing, refreshController } = useList(listRef, {
40+
onRefresh: updateData,
41+
});
42+
43+
return (
44+
<>
45+
<Text
46+
style={styles.title}
47+
>{`isRefreshing: ${isRefreshing} - Length: ${data.length}`}</Text>
48+
<SectionList
49+
ref={listRef}
50+
sections={data}
51+
keyExtractor={(item, index) => item + index}
52+
renderItem={({ item }) => <ListItem text={item} />}
53+
renderSectionHeader={({ section }) => (
54+
<Text style={styles.header}>{section.title}</Text>
55+
)}
56+
{...refreshController}
57+
/>
58+
</>
59+
);
60+
};
61+
62+
const styles = StyleSheet.create({
63+
header: {
64+
fontSize: 32,
65+
backgroundColor: '#fff',
66+
},
67+
title: {
68+
fontSize: 24,
69+
textAlign: 'center',
70+
marginVertical: 8,
71+
},
72+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, { useRef, useState } from 'react';
2+
import { VirtualizedList, StyleSheet, Text } from 'react-native';
3+
import { ListItem } from '../src/components';
4+
import { generateRandomColor } from '../src/utils/colors';
5+
import { useList } from 'react-native-use-list';
6+
7+
const list1 = generateRandomColor(5);
8+
const list2 = generateRandomColor(10);
9+
10+
export const VirtualizedListPullToRefreshExample = () => {
11+
const [data, setData] = useState(list1);
12+
13+
const getItem = (_x: any, index: number) => data[index] || '';
14+
15+
const getItemCount = () => data.length;
16+
17+
const listRef = useRef(null);
18+
19+
const updateData = () => {
20+
return new Promise((resolve) => {
21+
setTimeout(() => {
22+
setData(list2);
23+
resolve(true);
24+
}, 1000 * 2);
25+
});
26+
};
27+
28+
const { isRefreshing, refreshController } = useList(listRef, {
29+
onRefresh: updateData,
30+
});
31+
32+
return (
33+
<>
34+
<Text
35+
style={styles.title}
36+
>{`isRefreshing: ${isRefreshing} - Length: ${data.length}`}</Text>
37+
<VirtualizedList
38+
ref={listRef}
39+
initialNumToRender={4}
40+
renderItem={({ index }) => <ListItem text={index} />}
41+
keyExtractor={(item: string) => item}
42+
getItemCount={getItemCount}
43+
getItem={getItem}
44+
style={styles.list}
45+
{...refreshController}
46+
/>
47+
</>
48+
);
49+
};
50+
51+
const styles = StyleSheet.create({
52+
list: {
53+
flexGrow: 0,
54+
},
55+
title: {
56+
fontSize: 24,
57+
textAlign: 'center',
58+
marginVertical: 8,
59+
},
60+
});

‎example/src/components/ListItem.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Dimensions, StyleSheet, Text, View, ViewStyle } from 'react-native';
44
const { width } = Dimensions.get('screen');
55
type Props = {
66
text: string | number;
7-
isFocused: boolean;
7+
isFocused?: boolean;
88
style?: ViewStyle;
99
};
1010

‎src/index.tsx

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import type { List, Options } from './types';
22
import { usePagination } from './usePagination';
3+
import { usePullToRefresh } from './usePullToRefresh';
34

4-
export function useList(listRef: List, options?: Options) {
5-
const defaultOptions = { loopPages: false, debugMode: false }; // todo: merge options
5+
export function useList(listRef: List, options: Options = {}) {
6+
const defaultOptions = {
7+
loopPages: false,
8+
debugMode: false,
9+
onRefresh: undefined,
10+
};
611

7-
const pagination = usePagination(listRef, options || defaultOptions);
12+
options = Object.assign({ ...defaultOptions }, options);
813

9-
return { ...pagination };
14+
const pagination = usePagination(listRef, options);
15+
const pullToRefresh = usePullToRefresh(options);
16+
17+
return { ...pagination, ...pullToRefresh };
1018
}

‎src/types.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ export type List =
77
| RefObject<SectionList>;
88

99
export type Options = {
10-
loopPages?: boolean;
1110
debugMode?: boolean;
11+
} & PaginationOptions &
12+
PullToRefreshOptions;
13+
14+
export type PaginationOptions = {
15+
loopPages?: boolean;
16+
};
17+
18+
export type PullToRefreshOptions = {
19+
onRefresh?: () => Promise<any>;
1220
};
1321

1422
export enum Alignments {

‎src/usePagination.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ function getDataCountFromRef(listRef: List): number {
2828

2929
return listRef.current.props.data?.length || 0;
3030
}
31-
export function usePagination(listRef: List, options: Options) {
31+
export function usePagination(
32+
listRef: List,
33+
{ debugMode, loopPages }: Options
34+
) {
3235
const [pageIndex, setPageIndex] = useState(0);
3336
const [sectionItemIndex, setSectionItemIndex] = useState(0);
3437
const [sectionIndex, setSectionIndex] = useState(0);
@@ -40,18 +43,18 @@ export function usePagination(listRef: List, options: Options) {
4043
if (!listRef?.current) return;
4144

4245
const dataCount = getDataCountFromRef(listRef);
43-
if (options.debugMode && dataCount === 0)
46+
if (debugMode && dataCount === 0)
4447
return console.warn('Pagination does not work on empty lists.');
4548

4649
let index: number;
4750
if (typeof params === 'number') index = params;
4851
else index = params.index;
4952

5053
if (index < 0 || index >= dataCount) {
51-
if (options.loopPages) {
54+
if (loopPages) {
5255
index = (index + dataCount) % dataCount; // loop around
5356
} else {
54-
if (options.debugMode)
57+
if (debugMode)
5558
console.warn(
5659
`Page: ${index} is outside bounderies of 0 - ${dataCount}.`
5760
);

‎src/usePullToRefresh.tsx

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React, { useCallback, useState } from 'react';
2+
import { RefreshControl } from 'react-native';
3+
import type { Options } from './types';
4+
5+
export function usePullToRefresh({ onRefresh, debugMode }: Options) {
6+
const [isRefreshing, setIsRefreshing] = useState(false);
7+
8+
const _onRefresh = useCallback(async () => {
9+
if (!onRefresh) {
10+
if (debugMode)
11+
console.warn('onRefresh function is missing from the options.');
12+
return;
13+
}
14+
setIsRefreshing(true);
15+
await onRefresh();
16+
setIsRefreshing(false);
17+
}, [debugMode, onRefresh]);
18+
19+
const refreshController = {
20+
refreshControl: (
21+
<RefreshControl refreshing={isRefreshing} onRefresh={_onRefresh} />
22+
),
23+
};
24+
return {
25+
isRefreshing,
26+
refreshController,
27+
};
28+
}

0 commit comments

Comments
 (0)
Please sign in to comment.