SectionList
Volver a clases
Principiante

SectionList

60 min
50 vistas

SectionList

SectionList Básico

typescript
1import { SectionList, Text, View, StyleSheet } from "react-native";
2
3interface Contact {
4  id: string;
5  name: string;
6  phone: string;
7}
8
9interface ContactSection {
10  title: string;
11  data: Contact[];
12}
13
14const CONTACTS: ContactSection[] = [
15  {
16    title: "A",
17    data: [
18      { id: "1", name: "Ana García", phone: "123-456" },
19      { id: "2", name: "Antonio López", phone: "123-457" },
20    ],
21  },
22  {
23    title: "B",
24    data: [{ id: "3", name: "Beatriz Ruiz", phone: "123-458" }],
25  },
26  {
27    title: "C",
28    data: [
29      { id: "4", name: "Carlos Martín", phone: "123-459" },
30      { id: "5", name: "Carmen Pérez", phone: "123-460" },
31    ],
32  },
33];
34
35function ContactsList() {
36  const renderContact = ({ item }: { item: Contact }) => (
37    <View style={styles.contactItem}>
38      <Text style={styles.contactName}>{item.name}</Text>
39      <Text style={styles.contactPhone}>{item.phone}</Text>
40    </View>
41  );
42
43  const renderSectionHeader = ({ section }: { section: ContactSection }) => (
44    <View style={styles.sectionHeader}>
45      <Text style={styles.sectionHeaderText}>{section.title}</Text>
46    </View>
47  );
48
49  return (
50    <SectionList
51      sections={CONTACTS}
52      renderItem={renderContact}
53      renderSectionHeader={renderSectionHeader}
54      keyExtractor={(item) => item.id}
55      stickySectionHeadersEnabled={true}
56    />
57  );
58}
59
60const styles = StyleSheet.create({
61  contactItem: {
62    padding: 16,
63    backgroundColor: "white",
64    borderBottomWidth: 1,
65    borderBottomColor: "#eee",
66  },
67  contactName: {
68    fontSize: 16,
69    fontWeight: "600",
70    marginBottom: 4,
71  },
72  contactPhone: {
73    fontSize: 14,
74    color: "#666",
75  },
76  sectionHeader: {
77    backgroundColor: "#f5f5f5",
78    padding: 8,
79    paddingLeft: 16,
80  },
81  sectionHeaderText: {
82    fontSize: 14,
83    fontWeight: "bold",
84    color: "#666",
85  },
86});

Proyecto de la Semana: App de Contactos

typescript
1import { useState } from "react";
2import {
3  SectionList,
4  TextInput,
5  View,
6  Text,
7  TouchableOpacity,
8  StyleSheet,
9  SafeAreaView,
10  Alert,
11  Linking,
12} from "react-native";
13
14interface Contact {
15  id: string;
16  name: string;
17  phone: string;
18  email: string;
19}
20
21interface Section {
22  title: string;
23  data: Contact[];
24}
25
26export default function ContactsApp() {
27  const [contacts, setContacts] = useState<Contact[]>([
28    {
29      id: "1",
30      name: "Ana García",
31      phone: "123-456-7890",
32      email: "ana@example.com",
33    },
34    {
35      id: "2",
36      name: "Antonio López",
37      phone: "123-456-7891",
38      email: "antonio@example.com",
39    },
40    {
41      id: "3",
42      name: "Beatriz Ruiz",
43      phone: "123-456-7892",
44      email: "beatriz@example.com",
45    },
46    {
47      id: "4",
48      name: "Carlos Martín",
49      phone: "123-456-7893",
50      email: "carlos@example.com",
51    },
52  ]);
53  const [searchQuery, setSearchQuery] = useState("");
54
55  // Filtrar contactos
56  const filteredContacts = contacts.filter((contact) =>
57    contact.name.toLowerCase().includes(searchQuery.toLowerCase())
58  );
59
60  // Agrupar por letra
61  const sections: Section[] = filteredContacts
62    .reduce((acc, contact) => {
63      const firstLetter = contact.name[0].toUpperCase();
64      const section = acc.find((s) => s.title === firstLetter);
65
66      if (section) {
67        section.data.push(contact);
68      } else {
69        acc.push({ title: firstLetter, data: [contact] });
70      }
71
72      return acc;
73    }, [] as Section[])
74    .sort((a, b) => a.title.localeCompare(b.title));
75
76  const handleCall = (phone: string) => {
77    Linking.openURL(`tel:${phone}`);
78  };
79
80  const handleEmail = (email: string) => {
81    Linking.openURL(`mailto:${email}`);
82  };
83
84  const renderContact = ({ item }: { item: Contact }) => (
85    <View style={styles.contactCard}>
86      <View style={styles.avatar}>
87        <Text style={styles.avatarText}>
88          {item.name
89            .split(" ")
90            .map((n) => n[0])
91            .join("")
92            .toUpperCase()}
93        </Text>
94      </View>
95
96      <View style={styles.contactInfo}>
97        <Text style={styles.contactName}>{item.name}</Text>
98        <Text style={styles.contactPhone}>{item.phone}</Text>
99        <Text style={styles.contactEmail}>{item.email}</Text>
100      </View>
101
102      <View style={styles.actions}>
103        <TouchableOpacity
104          style={styles.actionButton}
105          onPress={() => handleCall(item.phone)}
106        >
107          <Text style={styles.actionText}>📱</Text>
108        </TouchableOpacity>
109        <TouchableOpacity
110          style={styles.actionButton}
111          onPress={() => handleEmail(item.email)}
112        >
113          <Text style={styles.actionText}>✉️</Text>
114        </TouchableOpacity>
115      </View>
116    </View>
117  );
118
119  const renderSectionHeader = ({ section }: { section: Section }) => (
120    <View style={styles.sectionHeader}>
121      <Text style={styles.sectionHeaderText}>{section.title}</Text>
122    </View>
123  );
124
125  return (
126    <SafeAreaView style={styles.container}>
127      <View style={styles.header}>
128        <Text style={styles.headerTitle}>Contactos</Text>
129        <Text style={styles.headerSubtitle}>
130          {filteredContacts.length} contacto
131          {filteredContacts.length !== 1 ? "s" : ""}
132        </Text>
133      </View>
134
135      <View style={styles.searchContainer}>
136        <TextInput
137          style={styles.searchInput}
138          placeholder="Buscar contactos..."
139          value={searchQuery}
140          onChangeText={setSearchQuery}
141        />
142      </View>
143
144      <SectionList
145        sections={sections}
146        renderItem={renderContact}
147        renderSectionHeader={renderSectionHeader}
148        keyExtractor={(item) => item.id}
149        stickySectionHeadersEnabled={true}
150        ListEmptyComponent={
151          <View style={styles.emptyContainer}>
152            <Text style={styles.emptyText}>No se encontraron contactos</Text>
153          </View>
154        }
155      />
156    </SafeAreaView>
157  );
158}
159
160const styles = StyleSheet.create({
161  container: {
162    flex: 1,
163    backgroundColor: "#f5f5f5",
164  },
165  header: {
166    backgroundColor: "white",
167    padding: 20,
168    borderBottomWidth: 1,
169    borderBottomColor: "#eee",
170  },
171  headerTitle: {
172    fontSize: 32,
173    fontWeight: "bold",
174    marginBottom: 4,
175  },
176  headerSubtitle: {
177    fontSize: 14,
178    color: "#666",
179  },
180  searchContainer: {
181    padding: 16,
182    backgroundColor: "white",
183    borderBottomWidth: 1,
184    borderBottomColor: "#eee",
185  },
186  searchInput: {
187    backgroundColor: "#f0f0f0",
188    padding: 12,
189    borderRadius: 8,
190    fontSize: 16,
191  },
192  contactCard: {
193    flexDirection: "row",
194    alignItems: "center",
195    backgroundColor: "white",
196    padding: 16,
197    borderBottomWidth: 1,
198    borderBottomColor: "#eee",
199  },
200  avatar: {
201    width: 50,
202    height: 50,
203    borderRadius: 25,
204    backgroundColor: "#007AFF",
205    justifyContent: "center",
206    alignItems: "center",
207  },
208  avatarText: {
209    color: "white",
210    fontSize: 18,
211    fontWeight: "bold",
212  },
213  contactInfo: {
214    flex: 1,
215    marginLeft: 12,
216  },
217  contactName: {
218    fontSize: 16,
219    fontWeight: "600",
220    marginBottom: 2,
221  },
222  contactPhone: {
223    fontSize: 14,
224    color: "#666",
225    marginBottom: 2,
226  },
227  contactEmail: {
228    fontSize: 12,
229    color: "#999",
230  },
231  actions: {
232    flexDirection: "row",
233    gap: 8,
234  },
235  actionButton: {
236    width: 40,
237    height: 40,
238    justifyContent: "center",
239    alignItems: "center",
240    backgroundColor: "#f0f0f0",
241    borderRadius: 20,
242  },
243  actionText: {
244    fontSize: 18,
245  },
246  sectionHeader: {
247    backgroundColor: "#f5f5f5",
248    padding: 8,
249    paddingLeft: 16,
250  },
251  sectionHeaderText: {
252    fontSize: 14,
253    fontWeight: "bold",
254    color: "#666",
255  },
256  emptyContainer: {
257    padding: 40,
258    alignItems: "center",
259  },
260  emptyText: {
261    fontSize: 16,
262    color: "#999",
263  },
264});