
Introduction
In mobile app development, displaying lists of data is one of the most common requirements. Whether it’s a social media feed, a shopping cart, or a contact list, efficient list rendering is crucial for app performance and user experience. React Native provides powerful components like FlatList and SectionList that are optimized for rendering large datasets with smooth scrolling performance. This chapter will explore Lists and Data Display and how to effectively use these components to create dynamic, performant lists in your React Native applications.
Why Not Use ScrollView for Lists?
Before diving into FlatList and SectionList, it’s important to understand why we don’t use ScrollView for large lists:
Problems with ScrollView for Large Lists:
- Memory Issues: Renders all items at once, consuming excessive memory
- Performance Degradation: Slow initial load times for large datasets
- Poor User Experience: Laggy scrolling with many items
- Battery Drain: Continuous rendering impacts device battery life
Solution: Virtualized Lists
React Native’s FlatList and SectionList use virtualization, which means they only render items currently visible on screen plus a small buffer, dramatically improving performance.
FlatList Fundamentals
FlatList is a performant interface for rendering basic, flat lists with the following features:
- Cross-platform compatibility
- Scroll loading (lazy loading)
- Pull-to-refresh functionality
- Header and footer support
- Horizontal and vertical scrolling
Basic FlatList Implementation
import React from 'react';
import { FlatList, Text, View, StyleSheet } from 'react-native';
const BasicFlatListExample = () => {
 const data = [
  { id: '1', title: 'First Item' },
  { id: '2', title: 'Second Item' },
  { id: '3', title: 'Third Item' },
  { id: '4', title: 'Fourth Item' },
  { id: '5', title: 'Fifth Item' },
 ];
 const renderItem = ({ item }) => (
  <View style={styles.item}>
   <Text style={styles.title}>{item.title}</Text>
  </View>
 );
 return (
  <FlatList
   data={data}
   renderItem={renderItem}
   keyExtractor={item => item.id}
  />
 );
};
const styles = StyleSheet.create({
 item: {
  backgroundColor: '#f9c2ff',
  padding: 20,
  marginVertical: 8,
  marginHorizontal: 16,
 },
 title: {
  fontSize: 16,
 },
});
export default BasicFlatListExample;
Essential FlatList Props
Required Props:
- data: Array of data to render
- renderItem: Function that renders each item
- keyExtractor: Function that extracts unique keys for items
Common Optional Props:
- ItemSeparatorComponent: Component rendered between items
- ListHeaderComponent: Component rendered at the top
- ListFooterComponent: Component rendered at the bottom
- ListEmptyComponent: Component rendered when data is empty
- horizontal: Boolean for horizontal scrolling
- numColumns: Number of columns for grid layout
Advanced FlatList Example
import React, { useState } from 'react';
import {
 FlatList,
 Text,
 View,
 StyleSheet,
 TouchableOpacity,
 RefreshControl,
 ActivityIndicator,
} from 'react-native';
const AdvancedFlatListExample = () => {
 const [data, setData] = useState([
  { id: '1', title: 'Item 1', description: 'Description for item 1' },
  { id: '2', title: 'Item 2', description: 'Description for item 2' },
  { id: '3', title: 'Item 3', description: 'Description for item 3' },
  { id: '4', title: 'Item 4', description: 'Description for item 4' },
  { id: '5', title: 'Item 5', description: 'Description for item 5' },
 ]);
Â
 const [refreshing, setRefreshing] = useState(false);
 const [loading, setLoading] = useState(false);
 const onRefresh = () => {
  setRefreshing(true);
  // Simulate API call
  setTimeout(() => {
   setData(prev => [...prev, {
    id: Date.now().toString(),
    title: 'New Item',
    description: 'Fresh content'
   }]);
   setRefreshing(false);
  }, 2000);
 };
 const loadMore = () => {
  if (loading) return;
  setLoading(true);
  // Simulate loading more data
  setTimeout(() => {
   const newData = Array.from({ length: 5 }, (_, i) => ({
    id: (Date.now() + i).toString(),
    title: `Item ${data.length + i + 1}`,
    description: `Description for item ${data.length + i + 1}`,
   }));
   setData(prev => [...prev, ...newData]);
   setLoading(false);
  }, 1500);
 };
 const renderItem = ({ item }) => (
  <TouchableOpacity style={styles.item}>
   <Text style={styles.title}>{item.title}</Text>
   <Text style={styles.description}>{item.description}</Text>
  </TouchableOpacity>
 );
 const renderSeparator = () => <View style={styles.separator} />;
 const renderHeader = () => (
  <View style={styles.header}>
   <Text style={styles.headerText}>My Awesome List</Text>
  </View>
 );
 const renderFooter = () => (
  <View style={styles.footer}>
   {loading && <ActivityIndicator size="large" color="#0000ff" />}
  </View>
 );
 const renderEmpty = () => (
  <View style={styles.emptyContainer}>
   <Text style={styles.emptyText}>No items found</Text>
  </View>
 );
 return (
  <FlatList
   data={data}
   renderItem={renderItem}
   keyExtractor={item => item.id}
   ItemSeparatorComponent={renderSeparator}
   ListHeaderComponent={renderHeader}
   ListFooterComponent={renderFooter}
   ListEmptyComponent={renderEmpty}
   refreshControl={
    <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
   }
   onEndReached={loadMore}
   onEndReachedThreshold={0.5}
   showsVerticalScrollIndicator={false}
  />
 );
};
const styles = StyleSheet.create({
 item: {
  backgroundColor: '#ffffff',
  padding: 20,
  marginHorizontal: 16,
 },
 title: {
  fontSize: 18,
  fontWeight: 'bold',
  marginBottom: 5,
 },
 description: {
  fontSize: 14,
  color: '#666',
 },
 separator: {
  height: 1,
  backgroundColor: '#e0e0e0',
  marginHorizontal: 16,
 },
 header: {
  padding: 20,
  backgroundColor: '#f0f0f0',
  alignItems: 'center',
 },
 headerText: {
  fontSize: 24,
  fontWeight: 'bold',
 },
 footer: {
  padding: 20,
  alignItems: 'center',
 },
 emptyContainer: {
  flex: 1,
  justifyContent: 'center',
  alignItems: 'center',
  padding: 40,
 },
 emptyText: {
  fontSize: 16,
  color: '#888',
 },
});
export default AdvancedFlatListExample;
Horizontal FlatList
import React from 'react';
import { FlatList, Text, View, StyleSheet, Dimensions } from 'react-native';
const HorizontalFlatListExample = () => {
 const { width } = Dimensions.get('window');
Â
 const data = Array.from({ length: 10 }, (_, i) => ({
  id: i.toString(),
  title: `Card ${i + 1}`,
  color: `hsl(${i * 36}, 70%, 50%)`,
 }));
 const renderItem = ({ item }) => (
  <View style={[styles.card, { backgroundColor: item.color }]}>
   <Text style={styles.cardTitle}>{item.title}</Text>
  </View>
 );
 return (
  <View style={styles.container}>
   <Text style={styles.sectionTitle}>Horizontal Scrolling Cards</Text>
   <FlatList
    data={data}
    renderItem={renderItem}
    keyExtractor={item => item.id}
    horizontal={true}
    showsHorizontalScrollIndicator={false}
    contentContainerStyle={styles.horizontalList}
    snapToInterval={width * 0.8 + 20}
    decelerationRate="fast"
    snapToAlignment="center"
   />
  </View>
 );
};
const styles = StyleSheet.create({
 container: {
  flex: 1,
  paddingTop: 50,
 },
 sectionTitle: {
  fontSize: 20,
  fontWeight: 'bold',
  marginBottom: 20,
  textAlign: 'center',
 },
 horizontalList: {
  paddingHorizontal: 10,
 },
 card: {
  width: Dimensions.get('window').width * 0.8,
  height: 200,
  marginRight: 20,
  borderRadius: 10,
  justifyContent: 'center',
  alignItems: 'center',
 },
 cardTitle: {
  fontSize: 24,
  fontWeight: 'bold',
  color: 'white',
 },
});
export default HorizontalFlatListExample;
Grid Layout with FlatList
import React from 'react';
import { FlatList, Text, View, StyleSheet, Dimensions } from 'react-native';
const GridFlatListExample = () => {
 const { width } = Dimensions.get('window');
 const numColumns = 2;
 const itemSize = (width - 48) / numColumns; // 48 = padding + margins
 const data = Array.from({ length: 20 }, (_, i) => ({
  id: i.toString(),
  title: `Item ${i + 1}`,
  color: `hsl(${i * 18}, 70%, 50%)`,
 }));
 const renderItem = ({ item }) => (
  <View style={[styles.gridItem, {
   width: itemSize,
   height: itemSize,
   backgroundColor: item.color
  }]}>
   <Text style={styles.gridItemText}>{item.title}</Text>
  </View>
 );
 return (
  <FlatList
   data={data}
   renderItem={renderItem}
   keyExtractor={item => item.id}
   numColumns={numColumns}
   contentContainerStyle={styles.gridContainer}
   columnWrapperStyle={styles.row}
  />
 );
};
const styles = StyleSheet.create({
 gridContainer: {
  padding: 16,
 },
 row: {
  justifyContent: 'space-between',
 },
 gridItem: {
  justifyContent: 'center',
  alignItems: 'center',
  marginBottom: 16,
  borderRadius: 8,
 },
 gridItemText: {
  color: 'white',
  fontWeight: 'bold',
  fontSize: 16,
 },
});
export default GridFlatListExample;
SectionList Fundamentals
SectionList is perfect for displaying data organized into logical sections, like contacts grouped by alphabet or items categorized by type.
Basic SectionList Implementation
import React from 'react';
import { SectionList, Text, View, StyleSheet } from 'react-native';
const BasicSectionListExample = () => {
 const DATA = [
  {
   title: 'Main dishes',
   data: ['Pizza', 'Burger', 'Risotto'],
  },
  {
   title: 'Sides',
   data: ['French Fries', 'Onion Rings', 'Fried Shrimps'],
  },
  {
   title: 'Drinks',
   data: ['Water', 'Coke', 'Beer'],
  },
  {
   title: 'Desserts',
   data: ['Cheese Cake', 'Ice Cream'],
  },
 ];
 const renderItem = ({ item }) => (
  <View style={styles.item}>
   <Text style={styles.title}>{item}</Text>
  </View>
 );
 const renderSectionHeader = ({ section: { title } }) => (
  <View style={styles.sectionHeader}>
   <Text style={styles.sectionHeaderText}>{title}</Text>
  </View>
 );
 return (
  <SectionList
   sections={DATA}
   keyExtractor={(item, index) => item + index}
   renderItem={renderItem}
   renderSectionHeader={renderSectionHeader}
  />
 );
};
const styles = StyleSheet.create({
 item: {
  backgroundColor: '#f9f9f9',
  padding: 20,
  marginVertical: 2,
  marginHorizontal: 16,
 },
 title: {
  fontSize: 16,
 },
 sectionHeader: {
  backgroundColor: '#e0e0e0',
  padding: 12,
  marginHorizontal: 16,
 },
 sectionHeaderText: {
  fontSize: 18,
  fontWeight: 'bold',
 },
});
export default BasicSectionListExample;
Advanced SectionList Example
import React, { useState } from 'react';
import {
 SectionList,
 Text,
 View,
 StyleSheet,
 TouchableOpacity,
 Alert,
} from 'react-native';
const AdvancedSectionListExample = () => {
 const [sections, setSections] = useState([
  {
   title: 'Fruits',
   data: [
    { id: '1', name: 'Apple', price: '$2.50' },
    { id: '2', name: 'Banana', price: '$1.20' },
    { id: '3', name: 'Orange', price: '$3.00' },
   ],
  },
  {
   title: 'Vegetables',
   data: [
    { id: '4', name: 'Carrot', price: '$1.80' },
    { id: '5', name: 'Broccoli', price: '$2.20' },
    { id: '6', name: 'Spinach', price: '$1.50' },
   ],
  },
  {
   title: 'Dairy',
   data: [
    { id: '7', name: 'Milk', price: '$3.50' },
    { id: '8', name: 'Cheese', price: '$5.00' },
   ],
  },
 ]);
 const handleItemPress = (item) => {
  Alert.alert('Item Selected', `You selected ${item.name} - ${item.price}`);
 };
 const renderItem = ({ item, section }) => (
  <TouchableOpacity
   style={styles.item}
   onPress={() => handleItemPress(item)}
  >
   <View style={styles.itemContent}>
    <Text style={styles.itemName}>{item.name}</Text>
    <Text style={styles.itemPrice}>{item.price}</Text>
   </View>
  </TouchableOpacity>
 );
 const renderSectionHeader = ({ section: { title, data } }) => (
  <View style={styles.sectionHeader}>
   <Text style={styles.sectionHeaderText}>{title}</Text>
   <Text style={styles.sectionCount}>({data.length} items)</Text>
  </View>
 );
 const renderSectionFooter = ({ section }) => (
  <View style={styles.sectionFooter}>
   <Text style={styles.sectionFooterText}>
    End of {section.title} section
   </Text>
  </View>
 );
 return (
  <SectionList
   sections={sections}
   keyExtractor={(item, index) => item.id}
   renderItem={renderItem}
   renderSectionHeader={renderSectionHeader}
   renderSectionFooter={renderSectionFooter}
   ItemSeparatorComponent={() => <View style={styles.separator} />}
   SectionSeparatorComponent={() => <View style={styles.sectionSeparator} />}
   stickySectionHeadersEnabled={true}
   showsVerticalScrollIndicator={false}
  />
 );
};
const styles = StyleSheet.create({
 item: {
  backgroundColor: '#ffffff',
  paddingHorizontal: 16,
  paddingVertical: 12,
 },
 itemContent: {
  flexDirection: 'row',
  justifyContent: 'space-between',
  alignItems: 'center',
 },
 itemName: {
  fontSize: 16,
  color: '#333',
 },
 itemPrice: {
  fontSize: 16,
  fontWeight: 'bold',
  color: '#2e7d32',
 },
 sectionHeader: {
  backgroundColor: '#1976d2',
  paddingHorizontal: 16,
  paddingVertical: 12,
  flexDirection: 'row',
  justifyContent: 'space-between',
  alignItems: 'center',
 },
 sectionHeaderText: {
  fontSize: 18,
  fontWeight: 'bold',
  color: 'white',
 },
 sectionCount: {
  fontSize: 14,
  color: '#bbdefb',
 },
 sectionFooter: {
  backgroundColor: '#f5f5f5',
  paddingHorizontal: 16,
  paddingVertical: 8,
  alignItems: 'center',
 },
 sectionFooterText: {
  fontSize: 12,
  color: '#666',
  fontStyle: 'italic',
 },
 separator: {
  height: 1,
  backgroundColor: '#e0e0e0',
  marginHorizontal: 16,
 },
 sectionSeparator: {
  height: 8,
  backgroundColor: '#f5f5f5',
 },
});
export default AdvancedSectionListExample;
Performance Optimization Techniques
1. Optimize renderItem Function
import React, { memo } from 'react';
import { Text, View, StyleSheet } from 'react-native';
// Memoize the item component to prevent unnecessary re-renders
const ListItem = memo(({ item, onPress }) => (
 <TouchableOpacity style={styles.item} onPress={() => onPress(item)}>
  <Text style={styles.title}>{item.title}</Text>
 </TouchableOpacity>
));
const OptimizedFlatList = () => {
 const [data, setData] = useState([]);
 // Use useCallback to prevent renderItem from being recreated on every render
 const renderItem = useCallback(({ item }) => (
  <ListItem item={item} onPress={handleItemPress} />
 ), []);
 const handleItemPress = useCallback((item) => {
  // Handle item press
 }, []);
 return (
  <FlatList
   data={data}
   renderItem={renderItem}
   keyExtractor={item => item.id}
   getItemLayout={(data, index) => (
    { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
   )}
   removeClippedSubviews={true}
   maxToRenderPerBatch={10}
   windowSize={10}
   initialNumToRender={10}
   updateCellsBatchingPeriod={50}
  />
 );
};
2. Use getItemLayout for Fixed Height Items
const ITEM_HEIGHT = 80;
const getItemLayout = (data, index) => ({
 length: ITEM_HEIGHT,
 offset: ITEM_HEIGHT * index,
 index,
});
// Apply to FlatList
<FlatList
 data={data}
 renderItem={renderItem}
 keyExtractor={item => item.id}
 getItemLayout={getItemLayout}
/>
3. Implement Proper Key Extraction
// Good - use unique, stable keys
const keyExtractor = (item, index) => item.id.toString();
// Bad - using index as key can cause issues
const keyExtractor = (item, index) => index.toString();
Common Patterns and Use Cases
1. Search with FlatList
import React, { useState, useMemo } from 'react';
import { FlatList, TextInput, View, StyleSheet } from 'react-native';
const SearchableList = () => {
 const [searchQuery, setSearchQuery] = useState('');
 const [originalData] = useState([
  { id: '1', name: 'John Doe', email: 'john@example.com' },
  { id: '2', name: 'Jane Smith', email: 'jane@example.com' },
  { id: '3', name: 'Bob Johnson', email: 'bob@example.com' },
 ]);
 const filteredData = useMemo(() => {
  if (!searchQuery) return originalData;
  return originalData.filter(item =>
   item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
   item.email.toLowerCase().includes(searchQuery.toLowerCase())
  );
 }, [searchQuery, originalData]);
 const renderItem = ({ item }) => (
  <View style={styles.item}>
   <Text style={styles.name}>{item.name}</Text>
   <Text style={styles.email}>{item.email}</Text>
  </View>
 );
 return (
  <View style={styles.container}>
   <TextInput
    style={styles.searchInput}
    placeholder="Search..."
    value={searchQuery}
    onChangeText={setSearchQuery}
   />
   <FlatList
    data={filteredData}
    renderItem={renderItem}
    keyExtractor={item => item.id}
   />
  </View>
 );
};
2. Swipe to Delete
import React, { useState } from 'react';
import {
 FlatList,
 View,
 Text,
 TouchableOpacity,
 StyleSheet,
 Alert,
} from 'react-native';
import { SwipeRow } from 'react-native-swipe-list-view';
const SwipeToDeleteList = () => {
 const [data, setData] = useState([
  { id: '1', title: 'Item 1' },
  { id: '2', title: 'Item 2' },
  { id: '3', title: 'Item 3' },
 ]);
 const deleteItem = (id) => {
  Alert.alert(
   'Delete Item',
   'Are you sure you want to delete this item?',
   [
    { text: 'Cancel', style: 'cancel' },
    {
     text: 'Delete',
     style: 'destructive',
     onPress: () => {
      setData(data.filter(item => item.id !== id));
     },
    },
   ]
  );
 };
 const renderItem = ({ item }) => (
  <SwipeRow rightOpenValue={-75}>
   <View style={styles.hiddenItem}>
    <TouchableOpacity
     style={styles.deleteButton}
     onPress={() => deleteItem(item.id)}
    >
     <Text style={styles.deleteButtonText}>Delete</Text>
    </TouchableOpacity>
   </View>
   <View style={styles.item}>
    <Text style={styles.title}>{item.title}</Text>
   </View>
  </SwipeRow>
 );
 return (
  <FlatList
   data={data}
   renderItem={renderItem}
   keyExtractor={item => item.id}
  />
 );
};
Best Practices
1. Memory Management
- Use removeClippedSubviews={true} for very long lists
- Implement getItemLayout for fixed-height items
- Use initialNumToRender to control initial batch size
- Set appropriate windowSize for your use case
2. Performance Optimization
- Memoize renderItem function with useCallback
- Use React.memo for list item components
- Avoid complex computations in renderItem
- Use keyExtractor with stable, unique keys
3. User Experience
- Implement pull-to-refresh functionality
- Add loading states for better UX
- Handle empty states gracefully
- Use appropriate separators and spacing
4. Data Structure
- Keep data flat and normalized
- Use consistent data structure across items
- Implement proper error handling for data fetching
- Consider pagination for large datasets
Common Pitfalls to Avoid
- Using array index as key: This can cause rendering issues when items are reordered
- Heavy computations in renderItem: Move complex logic outside the render function
- Not implementing pull-to-refresh: Users expect this functionality in mobile apps
- Ignoring empty states: Always provide feedback when lists are empty
- Not handling loading states: Show loading indicators during data fetching
- Overusing ScrollView: Use FlatList/SectionList for any list with more than a few items
Summary
This chapter covered the essential concepts of displaying lists in React Native using FlatList and SectionList. Key takeaways include:
- FlatList is ideal for simple, flat lists with virtualization for performance
- SectionList is perfect for grouped data with section headers
- Performance optimization is crucial for smooth scrolling
- Proper key extraction and memoization prevent unnecessary re-renders
- Advanced features like pull-to-refresh and infinite scrolling enhance user experience
- Grid layouts and horizontal scrolling provide flexible display options
In the next chapter, we’ll explore React Native Lifecycle Methods and Component Lifecycle, which will help you understand how components behave throughout their existence in your application.
Related Articles:
- React Native vs Flutter in 2024: A Detailed Comparison
- Top 10 React Native Libraries You Must Know in 2024
- React vs React Native: A Deep Dive into Key Differences
- How to Set Up Navigation in React Native : Step-by-Step Guide
- What is Axios? Fetch vs Axios: What’s the Difference?
- React Native Environment Setup: A Beginner’s Step-by-Step Guide
- React Native Web: A Step-by-Step Guide to Cross-Platform Development