useInfiniteScroll<T> function
InfiniteScrollResult<T>
useInfiniteScroll<T>({
- required Future<
PageResult< fetcher(T> >- int page
- bool immediate = true,
Create an infinite scroll data source.
Optimized for "load more" patterns where items accumulate.
final feed = useInfiniteScroll<Post>(
fetcher: (page) async {
final data = await api.getFeed(page: page);
return PageResult(items: data.posts, hasMore: data.hasMore);
},
);
// In build():
Column([
if (feed.initialLoading())
Spinner()
else
...feed.items().map((p) => PostCard(p)),
if (feed.loading() && !feed.initialLoading())
Spinner(), // Loading more indicator
if (feed.hasMore() && !feed.loading())
IntersectionObserver(onVisible: feed.loadMore),
]);
Implementation
InfiniteScrollResult<T> useInfiniteScroll<T>({
required Future<PageResult<T>> Function(int page) fetcher,
bool immediate = true,
}) {
final items = ref<List<T>>([]);
final loading = ref(false);
final initialLoading = ref(true);
final error = ref<String?>(null);
final hasMore = ref(true);
final currentPage = ref(0);
Future<void> loadMore() async {
if (loading() || !hasMore()) return;
loading.value = true;
error.value = null;
try {
final nextPage = currentPage() + 1;
final result = await fetcher(nextPage);
items.value = [...items(), ...result.items];
currentPage.value = nextPage;
hasMore.value = result.hasMore;
} catch (e) {
error.value = e.toString();
} finally {
loading.value = false;
initialLoading.value = false;
}
}
Future<void> refresh() async {
items.value = [];
currentPage.value = 0;
hasMore.value = true;
initialLoading.value = true;
await loadMore();
}
if (immediate) {
loadMore();
}
return (
items: items,
loading: loading,
initialLoading: initialLoading,
error: error,
hasMore: hasMore,
loadMore: loadMore,
refresh: refresh,
);
}