Skip to content

1.6 Lists

Learning Objectives

  • Understand why FlatList is superior to .map() for rendering lists
  • Render dynamic lists efficiently with proper keys and data extraction
  • Implement in-memory CRUD operations (Create, Read, Update, Delete) using state
  • Build a complete todo/task app with add, delete, and toggle functionality

In this lesson, you’ll learn how to manage lists of data, which is the foundation of almost every real mobile app, from todo lists to Instagram feeds to shopping carts.

You might be thinking: “Can’t I just use .map() to render a list?” Yes, but FlatList is way better for mobile apps.

When you use .map() to render a list, React Native renders every single item, even if they’re off-screen:

// ❌ This renders ALL items, even if you have 10,000
<ScrollView>
{todos.map((todo) => (
<Text key={todo.id}>{todo.title}</Text>
))}
</ScrollView>

If you have 1,000 items, React Native will:

  • Create 1,000 components
  • Render 1,000 views
  • Keep 1,000 items in memory

This kills performance on mobile devices.

FlatList 1 only renders what’s visible on screen. As you scroll, it recycles off-screen items and reuses them for new items:

// ✅ This only renders visible items (maybe 10-20 at a time)
<FlatList
data={todos}
renderItem={({ item }) => <Text>{item.title}</Text>}
keyExtractor={(item) => item.id}
/>
Diagram

Key benefits:

  • Only renders visible items
  • Uses far less memory (10 items vs 1,000)
  • Smoother scrolling, even with huge lists
  • Less CPU usage = better battery life
Deeper Dive: FlatList Virtualization

FlatList uses a technique called windowing or virtualization. Here’s what happens:

  1. FlatList calculates how many items fit on screen (based on item height) and renders those + a few extras above/below (called the “overscan”)

  2. As you scroll, FlatList:

    • Unmounts items that moved far off-screen
    • Mounts new items that are about to appear
    • Reuses component instances (recycles them)
  3. Only ~10-30 components exist at any time, regardless of how many items are in data

FlatList requires three essential props:

PropTypePurpose
dataArray<T>The array of items to render
renderItem({ item }) => JSX.ElementFunction that renders each item
keyExtractor(item, index) => stringFunction that returns unique key (ID)
// 1. Define your data type
interface Item {
id: string;
name: string;
}
// 2. Create data array
const DATA: Item[] = [
{ id: '1', name: 'React Native' },
{ id: '2', name: 'TypeScript' },
{ id: '3', name: 'Expo' },
];
// 3. Define how to render each item
const renderItem = ({ item }: { item: Item }) => (
<View style={styles.item}>
<Text>{item.name}</Text>
</View>
);
// 4. Use FlatList
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>;

What’s happening here:

  1. data={DATA} → FlatList reads your array
  2. For each visible item, FlatList calls renderItem({ item })
  3. keyExtractor tells React which item is which (more on this next)

Keys are critical for React’s performance. When you add, remove, or reorder items, React uses keys to figure out what changed.

// ❌ NEVER DO THIS
keyExtractor={(item, index) => index.toString()}

Why is this bad? When you delete an item, all the indices shift:

Before delete:
[0] "Buy milk" → key: "0"
[1] "Walk dog" → key: "1" ← Delete this
[2] "Study React" → key: "2"
After delete:
[0] "Buy milk" → key: "0"
[1] "Study React" → key: "1" ← Same key, but different item!
Deeper Dive: Why React Uses Keys for Reconciliation

When state changes, React needs to figure out what changed in the UI. This process is called reconciliation.

Without keys, React compares components by position:

Old: [<Item title="A" />, <Item title="B" />, <Item title="C" />]
New: [<Item title="A" />, <Item title="C" />] ← B was deleted

React sees: position 0 and 1 still have items, but position 2 is gone. It assumes items didn’t change, only the last one was removed. This is wrong!

With keys, React compares by ID:

Old: [<Item key="1" />, <Item key="2" />, <Item key="3" />]
New: [<Item key="1" />, <Item key="3" />] ← key="2" is gone

React sees: key="1" and key="3" still exist, but key="2" is missing. It correctly identifies that item 2 was deleted and reuses the existing components for items 1 and 3.

For more details, see Why does React need keys?

// ✅ Use unique, stable IDs
keyExtractor={(item) => item.id}

Each item has a permanent ID that never changes, even when items are added, removed, or reordered:

Before delete:
[id: "abc123"] "Buy milk"
[id: "def456"] "Walk dog" ← Delete this
[id: "ghi789"] "Study React"
After delete:
[id: "abc123"] "Buy milk"
[id: "ghi789"] "Study React"

React sees id="def456" is gone and id="ghi789" still exists. It removes the correct component and keeps the right one.

For new items, use one of these methods:

// Method 1: Date.now() (simple, good for single-user apps)
const newItem = {
id: Date.now().toString(),
title: 'New task',
};
// Method 2: uuid library (best for production apps)
import { v4 as uuidv4 } from 'uuid';
const newItem = {
id: uuidv4(), // e.g., "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
title: 'New task',
};

These four exercises focus on independent CRUD operations. Complete each one separately as they don’t build on each other.

  1. Click the external link icon (top-left of each Snack) to open in a new tab.
  2. Click the blue “Save” button to save to your account.
  3. Copy the Snack URL once completed and submit on Moodle.

Display a pre-populated list of fruits using FlatList. This exercise focuses on the Read operation so you’ll just be rendering data that already exists.

  • A Fruit interface is provided with id, name, and emoji properties
  • An array of 5 fruits is provided as FRUITS
  • Create a FlatList component with:
    • data={FRUITS}
    • keyExtractor that returns item.id
    • renderItem that displays the emoji (large) and name

Hints

  1. The FlatList component needs three essential props. Look at the basic example earlier in the lesson. What three props does every FlatList need?

  2. The keyExtractor function should return a unique identifier for each fruit. Since each fruit has an id property, use that. The function receives an item parameter.

  3. The renderItem function receives { item } as a parameter and should return JSX that displays the fruit. You can access item.emoji and item.name to show the data.

  4. Make the emoji large (font size 40-48) and add some spacing between items. Center the content for a clean look.

  • FlatList uses virtualization to only render visible items, making it vastly more performant than .map() for lists with many items.
  • data (array), renderItem (function), and keyExtractor (returns unique ID).
  • Never use array index as a key, always use stable, unique IDs so React can correctly identify which items changed.
  • Create with [...items, newItem], Read with FlatList rendering, Update with .map() to find and modify, Delete with .filter() to remove.
  • Always create new arrays when updating state, never mutate with .push(), .splice(), or direct assignment.
  • Always handle the case when your list is empty to improve UX.
  1. FlatList - React Native Docs