All skills

Complete React Native and Expo optimization guide combining Callstack profiling with Vercel code patterns. Covers FPS, TTI, bundle size, memory, lists, animations, state, UI, and React Compiler. Use for any React Native performance, debugging, or best practices task.

Use this Skill: https://skilld.dev/gh/gigs-slc/react-native-skills/react-native

This session only. Nothing lands on disk.

referencesjs-lists-flatlist-flashlist.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Skill: Higher-Order Lists

Replace ScrollView with FlatList or FlashList for performant large list rendering.

Quick Pattern

Incorrect:

<ScrollView>
  {items.map((item) => <Item key={item.id} {...item} />)}
</ScrollView>

Correct:

<FlashList
  data={items}
  keyExtractor={(item) => item.id}
  renderItem={({ item }) => <Item {...item} />}
  estimatedItemSize={50}
/>

When to Use

  • Rendering more than 10-20 items in a list
  • List scrolling is choppy or laggy
  • App freezes when loading list data
  • Memory usage spikes with long lists

Prerequisites

  • @shopify/flash-list for FlashList (recommended)
  • Understanding of list virtualization

Step-by-Step Instructions

1. Identify the Problem

FPS Drop Graph

The FPS graph shows a severe performance problem during list rendering:

  • FPS starts at ~60 (smooth)
  • Drops to ~3 FPS during heavy list operation
  • Recovers after rendering completes
// BAD: ScrollView renders ALL items at once
const BadList = ({ items }) => (
  <ScrollView>
    {items.map((item, index) => (
      <View key={index}>
        <Text>{item}</Text>
      </View>
    ))}
  </ScrollView>
);

With 5000 items, this creates 5000 views immediately, causing:

  • Multi-second freeze
  • FPS drop to 0
  • High memory usage

2. Replace with FlatList

import { FlatList } from 'react-native';

const BetterList = ({ items }) => {
  const renderItem = ({ item }) => (
    <View>
      <Text>{item}</Text>
    </View>
  );
  
  return (
    <FlatList
      data={items}
      renderItem={renderItem}
      keyExtractor={(item, index) => index.toString()}
    />
  );
};

FlatList only renders visible items + buffer (windowing).

3. Optimize FlatList with getItemLayout

For fixed-height items, skip layout measurement:

const ITEM_HEIGHT = 50;

const OptimizedList = ({ items }) => {
  const renderItem = ({ item }) => (
    <View style={{ height: ITEM_HEIGHT }}>
      <Text>{item}</Text>
    </View>
  );
  
  const getItemLayout = (_, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  });
  
  return (
    <FlatList
      data={items}
      renderItem={renderItem}
      keyExtractor={(item, index) => index.toString()}
      getItemLayout={getItemLayout}
    />
  );
};

4. Upgrade to FlashList (Best Performance)

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

const BestList = ({ items }) => {
  const renderItem = ({ item }) => (
    <View style={{ height: 50 }}>
      <Text>{item}</Text>
    </View>
  );
  
  return (
    <FlashList
      data={items}
      renderItem={renderItem}
      estimatedItemSize={50}  // Required for FlashList
    />
  );
};

FlashList advantages:

  • Recycles views instead of creating new ones
  • 78/100 vs 25/100 performance score in benchmarks
  • Smoother scrolling at ~54 FPS vs lower for FlatList

Code Examples

Variable Height Items

// Calculate average for estimatedItemSize
// Items are 50px, 100px, 150px
// Average: (50 + 100 + 150) / 3 = 100px

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={100}
/>

Mixed Item Types

<FlashList
  data={items}
  renderItem={({ item }) => {
    if (item.type === 'header') return <Header {...item} />;
    if (item.type === 'product') return <Product {...item} />;
    return <DefaultItem {...item} />;
  }}
  getItemType={(item) => item.type}  // Helps recycling
  estimatedItemSize={80}
/>

FlatList Optimizations (if not using FlashList)

<FlatList
  data={items}
  renderItem={renderItem}
  // Performance props
  removeClippedSubviews={true}
  maxToRenderPerBatch={10}
  updateCellsBatchingPeriod={50}
  initialNumToRender={10}
  windowSize={5}
  // Avoid re-renders
  keyExtractor={(item) => item.id}
  extraData={selectedId}  // Only when selection changes
/>

Performance Comparison

Component 5000 Items Load Scroll FPS Memory
ScrollView 1-3 seconds freeze < 30 High
FlatList ~100ms ~45 Medium
FlashList ~50ms ~54 Low

Decision Matrix

Scenario Recommendation
< 20 static items ScrollView OK
20-100 items FlatList minimum
> 100 items FlashList
Complex item layouts FlashList with getItemType
Fixed height items Add getItemLayout or estimatedItemSize

Common Pitfalls

  • Inline renderItem functions: Causes re-renders. Define outside or use useCallback.
  • Missing keyExtractor: Use unique IDs, not array index when possible.
  • Ignoring estimatedItemSize warning: FlashList warns if not set. Always provide it.
  • Heavy item components: Keep list items light. Move side effects out.

Related Skills

Source: SKILL.md on GitHub

2 alerts6mo4 checks · Risk HIGH
  • Gen Agent Trust Hub7mo

    The skill is a technical React Native guide that includes a high-risk remote code execution pattern for installing a performance tool.

  • Socket6mo

    No alerts

  • Snyk7mo

    Risk: MEDIUM · No issues

  • Runlayer7mo

    7/67 files flagged

Signed by skilld at 1d299ca. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Dormantupdated 8 months ago
Other metadata
metadata
{
  "author": "Callstack + Vercel",
  "version": "1.0.0",
  "tags": "react-native, expo, performance, optimization, patterns"
}

README badge

README badge for gigs-slc/react-native-skills/react-native