Skip to content

Instantly share code, notes, and snippets.

@Blithe-Chiang
Last active September 17, 2025 08:53
Show Gist options
  • Select an option

  • Save Blithe-Chiang/9fc2b184dd11456a9337354267d4339f to your computer and use it in GitHub Desktop.

Select an option

Save Blithe-Chiang/9fc2b184dd11456a9337354267d4339f to your computer and use it in GitHub Desktop.
CFlatList
import {useLatest, useMemoizedFn, useMount, useUnmountedRef} from 'ahooks';
import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react';
import {FlatList, FlatListProps} from 'react-native';
interface CFlatListProps<ItemT> {
onFetch: (
isRefresh: boolean,
success: (data: ItemT[], pageSize: number) => void,
fail: (err: string) => void,
) => void;
fetchOnMounted?: boolean;
}
export interface CFlatListRef<ItemT> {
updateListData: (updater: (prevData: ItemT[]) => ItemT[]) => void;
getListData: () => ItemT[];
scrollToTop: () => void;
}
function CFlatList<ItemT>(
props: CFlatListProps<ItemT> &
Omit<FlatListProps<ItemT>, 'data' | 'onRefresh'>,
ref: React.Ref<CFlatListRef<ItemT>>,
) {
const {onFetch, fetchOnMounted = true, onEndReached, ...restProps} = props;
const [listData, setListData] = useState<ItemT[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [hasMore, setHasMore] = useState(false);
const unmountedRef = useUnmountedRef();
const listDataRef = useLatest(listData);
useMount(() => {
if (fetchOnMounted) {
_onFetch(true);
}
});
const success = useMemoizedFn(
(_data: ItemT[], pageSize: number, isRefresh: boolean) => {
if (unmountedRef.current) return;
if (isRefresh) {
setListData(_data);
} else {
setListData(prevData => [...prevData, ..._data]);
}
const hasMore = _data.length >= pageSize;
setHasMore(hasMore);
setRefreshing(false);
},
);
const fail = useMemoizedFn(() => {
if (unmountedRef.current) return;
setRefreshing(false);
});
const _onFetch = useMemoizedFn((isRefresh = false) => {
if (!isRefresh && !hasMore) return;
onFetch(
isRefresh,
(data, pageSize) => success(data, pageSize, isRefresh),
fail,
);
});
useImperativeHandle(
ref,
() => ({
updateListData: (updater: (prevData: ItemT[]) => ItemT[]) => {
setListData(prevData => updater(prevData));
},
getListData: () => listDataRef.current,
scrollToTop: () => {
flatListRef.current?.scrollToIndex({
animated: true,
index: 0,
});
},
}),
[listDataRef],
);
const flatListRef = useRef<FlatList<ItemT>>(null);
return (
<FlatList
ref={flatListRef}
data={listData}
refreshing={refreshing}
onRefresh={() => _onFetch(true)}
onEndReached={info => {
onEndReached?.(info);
_onFetch(false);
}}
{...restProps}
/>
);
}
export default forwardRef(CFlatList) as <ItemT>(
props: CFlatListProps<ItemT> &
Omit<FlatListProps<ItemT>, 'data' | 'onRefresh'> & {
ref?: React.Ref<CFlatListRef<ItemT>>;
},
) => React.ReactElement;
import { Text, View } from "react-native";
import CFlatList from "./CFlatList";
export default function Index() {
const onFetch = (isRefresh: boolean, success: (data: any[], pageSize: number) => void, fail: (err: string) => void): void => {
if (isRefresh) {
success(getData(20), 20);
return
}
success(getData(10), 10)
};
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<CFlatList<{ id: number, title: string }>
keyExtractor={(item) => item.id + ""}
renderItem={({ item }) => <Text style={{
height: 50,
}}>{item.title}</Text>}
onFetch={onFetch} />
</View>
);
}
function getData(size: number = 10) {
// generate random values
return Array.from({ length: size }, (_, i) => {
const id = Math.random();
return {
title: `title ${id + 1}`,
id: id + 1,
};
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment