Claude-skill-registry flashlist-patterns

FlashList high-performance list patterns. Use when implementing lists.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/flashlist-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-flashlist-patterns && rm -rf "$T"
manifest: skills/data/flashlist-patterns/SKILL.md
source content

FlashList Patterns Skill

This skill covers Shopify's FlashList for high-performance lists.

When to Use

Use this skill when:

  • Implementing any scrollable list
  • Replacing FlatList
  • Lists have 50+ items
  • List performance is critical

Core Principle

FLASHLIST ALWAYS - FlashList is 10x faster than FlatList. Use it for all lists.

Installation

npm install @shopify/flash-list

Basic Usage

import { FlashList } from '@shopify/flash-list';

interface Item {
  id: string;
  title: string;
}

function ItemList({ items }: { items: Item[] }): React.ReactElement {
  return (
    <FlashList
      data={items}
      renderItem={({ item }) => (
        <View className="p-4 border-b border-gray-200">
          <Text className="text-lg">{item.title}</Text>
        </View>
      )}
      estimatedItemSize={60}  // Required!
      keyExtractor={(item) => item.id}
    />
  );
}

Required Props

estimatedItemSize

// Required for FlashList to calculate recycling
<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={80}  // Average height of items in pixels
/>

Performance Optimization

Memoize renderItem

import { useCallback, memo } from 'react';

const ItemCard = memo(function ItemCard({ item }: { item: Item }) {
  return (
    <View className="p-4">
      <Text>{item.title}</Text>
    </View>
  );
});

function ItemList({ items }: { items: Item[] }): React.ReactElement {
  const renderItem = useCallback(
    ({ item }: { item: Item }) => <ItemCard item={item} />,
    []
  );

  return (
    <FlashList
      data={items}
      renderItem={renderItem}
      estimatedItemSize={60}
    />
  );
}

Use keyExtractor

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={60}
  keyExtractor={(item) => item.id}  // Unique stable key
/>

Different Item Types

getItemType

interface ListItem {
  id: string;
  type: 'header' | 'item' | 'separator';
  data: unknown;
}

<FlashList
  data={items}
  renderItem={({ item }) => {
    switch (item.type) {
      case 'header':
        return <HeaderComponent data={item.data} />;
      case 'separator':
        return <SeparatorComponent />;
      default:
        return <ItemComponent data={item.data} />;
    }
  }}
  getItemType={(item) => item.type}  // Enables better recycling
  estimatedItemSize={60}
/>

overrideItemLayout

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={60}
  overrideItemLayout={(layout, item) => {
    // Set exact size for different item types
    if (item.type === 'header') {
      layout.size = 100;
    } else if (item.type === 'separator') {
      layout.size = 20;
    } else {
      layout.size = 60;
    }
  }}
/>

Horizontal Lists

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={150}
  horizontal
/>

Grid Layout

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={180}
  numColumns={2}
/>

Pull to Refresh

import { useState } from 'react';
import { RefreshControl } from 'react-native';

function RefreshableList(): React.ReactElement {
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = async () => {
    setRefreshing(true);
    await fetchNewData();
    setRefreshing(false);
  };

  return (
    <FlashList
      data={items}
      renderItem={renderItem}
      estimatedItemSize={60}
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
      }
    />
  );
}

Infinite Scroll

function InfiniteList(): React.ReactElement {
  const [items, setItems] = useState<Item[]>([]);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);

  const loadMore = async () => {
    if (loading || !hasMore) return;

    setLoading(true);
    const newItems = await fetchMoreItems();

    if (newItems.length === 0) {
      setHasMore(false);
    } else {
      setItems((prev) => [...prev, ...newItems]);
    }
    setLoading(false);
  };

  return (
    <FlashList
      data={items}
      renderItem={renderItem}
      estimatedItemSize={60}
      onEndReached={loadMore}
      onEndReachedThreshold={0.5}
      ListFooterComponent={loading ? <ActivityIndicator /> : null}
    />
  );
}

Empty State

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={60}
  ListEmptyComponent={
    <View className="flex-1 items-center justify-center p-8">
      <Text className="text-gray-500">No items found</Text>
    </View>
  }
/>

Headers and Footers

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={60}
  ListHeaderComponent={
    <View className="p-4 bg-gray-100">
      <Text className="text-xl font-bold">Items</Text>
    </View>
  }
  ListFooterComponent={
    <View className="p-4">
      <Text className="text-gray-500 text-center">End of list</Text>
    </View>
  }
/>

Sticky Headers

interface Section {
  title: string;
  data: Item[];
}

// Use SectionList-like pattern
<FlashList
  data={flattenedData}
  renderItem={({ item }) => {
    if (item.isHeader) {
      return <StickyHeader title={item.title} />;
    }
    return <ItemCard item={item} />;
  }}
  stickyHeaderIndices={headerIndices}
  estimatedItemSize={60}
  getItemType={(item) => (item.isHeader ? 'header' : 'item')}
/>

Scroll to Item

import { useRef } from 'react';
import { FlashList } from '@shopify/flash-list';

function ScrollableList(): React.ReactElement {
  const listRef = useRef<FlashList<Item>>(null);

  const scrollToTop = () => {
    listRef.current?.scrollToOffset({ offset: 0, animated: true });
  };

  const scrollToIndex = (index: number) => {
    listRef.current?.scrollToIndex({ index, animated: true });
  };

  return (
    <>
      <FlashList
        ref={listRef}
        data={items}
        renderItem={renderItem}
        estimatedItemSize={60}
      />
      <Button onPress={scrollToTop}>Scroll to Top</Button>
    </>
  );
}

Common Props

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={60}
  keyExtractor={(item) => item.id}
  getItemType={(item) => item.type}
  numColumns={1}
  horizontal={false}
  inverted={false}
  showsVerticalScrollIndicator={true}
  showsHorizontalScrollIndicator={false}
  onEndReached={loadMore}
  onEndReachedThreshold={0.5}
  refreshControl={<RefreshControl />}
  ListHeaderComponent={<Header />}
  ListFooterComponent={<Footer />}
  ListEmptyComponent={<EmptyState />}
  ItemSeparatorComponent={() => <View className="h-px bg-gray-200" />}
/>

Debug Mode

// Enable debug logs in development
<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={60}
  // Debug overlay shows recycling info
/>

Notes

  • estimatedItemSize
    is required
  • Use
    getItemType
    for different item layouts
  • Memoize renderItem with useCallback
  • Use React.memo for item components
  • FlashList recycles views for performance
  • Test with large datasets (1000+ items)