AniUI

Infinite List

FlatList wrapper with automatic load-more on scroll.

Installation#

npx @aniui/cli add infinite-list

Build UI components

Task 1

Write unit tests

Task 2

Deploy to production

Task 3

Web preview — components render natively on iOS & Android
import { InfiniteList } from "@/components/ui/infinite-list";

export function MyScreen() {
  const [data, setData] = useState(initialItems);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  const loadMore = async () => {
    setLoading(true);
    const newItems = await fetchMore(data.length);
    setData((prev) => [...prev, ...newItems]);
    setHasMore(newItems.length > 0);
    setLoading(false);
  };

  return (
    <InfiniteList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View className="p-4 border-b border-border">
          <Text className="text-foreground">{item.title}</Text>
        </View>
      )}
      onLoadMore={loadMore}
      hasMore={hasMore}
      loading={loading}
      threshold={0.5}
    />
  );
}

Usage#

app/index.tsx
import { InfiniteList } from "@/components/ui/infinite-list";

export function MyScreen() {
  const [data, setData] = useState(initialItems);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  const loadMore = async () => {
    setLoading(true);
    const newItems = await fetchMore(data.length);
    setData((prev) => [...prev, ...newItems]);
    setHasMore(newItems.length > 0);
    setLoading(false);
  };

  return (
    <InfiniteList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View className="p-4 border-b border-border">
          <Text className="text-foreground">{item.title}</Text>
        </View>
      )}
      onLoadMore={loadMore}
      hasMore={hasMore}
      loading={loading}
      threshold={0.5}
    />
  );
}

Props#

PropTypeDefault
data
T[]
-
renderItem
({ item, index }) => ReactElement
-
keyExtractor
(item, index) => string
-
onLoadMore
() => void
-
hasMore
boolean
false
loading
boolean
false
threshold
number
0.5
className
string
-

Also accepts all FlatList props.

Accessibility#

  • FlatList with auto-load-more on scroll.
  • List items are individually focusable by screen readers.

Source#

components/ui/infinite-list.tsx
import React from "react";
import { FlatList, View, ActivityIndicator } from "react-native";
import { cn } from "@/lib/utils";

export interface InfiniteListProps<T> extends React.ComponentPropsWithoutRef<typeof FlatList<T>> {
  className?: string;
  data: T[];
  renderItem: ({ item, index }: { item: T; index: number }) => React.ReactElement;
  keyExtractor: (item: T, index: number) => string;
  onLoadMore?: () => void;
  hasMore?: boolean;
  loading?: boolean;
  threshold?: number;
}

export function InfiniteList<T>({
  className,
  data,
  renderItem,
  keyExtractor,
  onLoadMore,
  hasMore = false,
  loading = false,
  threshold = 0.5,
  ...props
}: InfiniteListProps<T>) {
  return (
    <FlatList
      className={cn("", className)}
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      onEndReached={hasMore && !loading ? onLoadMore : undefined}
      onEndReachedThreshold={threshold}
      ListFooterComponent={
        loading ? (
          <View className="py-4 items-center">
            <ActivityIndicator size="small" color="hsl(240 5.9% 10%)" accessibilityRole="progressbar" />
          </View>
        ) : null
      }
      {...props}
    />
  );
}