SectionList y Datos Agrupados
Encabezados, pies de página y encabezados de sección fijos para contactos alfabetizados, configuraciones categorizadas y feeds de línea de tiempo.
Busca en todas las páginas de la documentación
Encabezados, pies de página y encabezados de sección fijos para contactos alfabetizados, configuraciones categorizadas y feeds de línea de tiempo.
Tarjeta de referencia rápida - lista para copiar y pegar.
import { SectionList, StyleSheet, Text, View } from "react-native";
interface Contact {
id: string;
name: string;
}
interface ContactSection {
title: string;
data: Contact[];
}
const SECTIONS: ContactSection[] = [
{
title: "A",
data: [
{ id: "a1", name: "Alex Rivera" },
{ id: "a2", name: "Avery Chen" },
],
},
{
title: "B",
data: [{ id: "b1", name: "Blake Morgan" }],
},
];
export function ContactSectionList() {
return (
<SectionList
sections={SECTIONS}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.name}>{item.name}</Text>
</View>
)}
renderSectionHeader={({ section }) => (
<View style={styles.header}>
<Text style={styles.headerText}>{section.title}</Text>
</View>
)}
stickySectionHeadersEnabled
ItemSeparatorComponent={() => <View style={styles.separator} />}
SectionSeparatorComponent={() => <View style={styles.sectionGap} />}
/>
);
}
const styles = StyleSheet.create({
header: {
backgroundColor: "#f1f5f9",
paddingHorizontal: 16,
paddingVertical: 6,
},
headerText: { fontSize: 13, fontWeight: "700", color: "#475569" },
row: { paddingHorizontal: 16, paddingVertical: 12, backgroundColor: "#fff" },
name: { fontSize: 16, color: "#0f172a" },
separator: { height: StyleSheet.hairlineWidth, backgroundColor: "#e2e8f0" },
sectionGap: { height: 8 },
});Cuándo usarlo: Los datos se agrupan naturalmente en secciones etiquetadas - contactos por letra, configuraciones por categoría, actividad por fecha.
import { useMemo } from "react";
import { SectionList, StyleSheet, Text, View, type SectionListData } from "react-native";
interface SettingItem {
id: string;
label: string;
value?: string;
}
interface SettingSection extends SectionListData<SettingItem> {
title: string;
data: SettingItem[];
}
const SETTINGS: SettingItem[] = [
{ id: "profile", label: "Profile", value: "Alex Rivera" },
{ id: "email", label: "Email", value: "alex@example.com" },
{ id: "push", label: "Push notifications", value: "On" },
{ id: "dark", label: "Dark mode", value: "System" },
{ id: "language", label: "Language", value: "English" },
{ id: "privacy", label: "Privacy policy" },
{ id: "terms", label: "Terms of service" },
];
function groupSettings(items: SettingItem[]): SettingSection[] {
const account = items.filter((i) => ["profile", "email"].includes(i.id));
const preferences = items.filter((i) => ["push", "dark", "language"].includes(i.id));
const legal = items.filter((i) => ["privacy", "terms"].includes(i.id));
return [
{ title: "Account", data: account },
{ title: "Preferences", data: preferences },
{ title: "Legal", data: legal },
].filter((section) => section.data.length > 0);
}
function SectionHeader({ title }: { title: string }) {
return (
<View style={styles.sectionHeader}>
<Text style={styles.sectionHeaderText}>{title}</Text>
</View>
);
}
function SettingRow({ item }: { item: SettingItem }) {
return (
<View style={styles.row}>
<Text style={styles.label}>{item.label}</Text>
{item.value ? <Text style={styles.value}>{item.value}</Text> : null}
</View>
);
}
export default function SettingsScreen() {
const sections = useMemo(() => groupSettings(SETTINGS), []);
return (
<SectionList
sections={sections}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <SettingRow item={item} />}
renderSectionHeader={({ section }) => <SectionHeader title={section.title} />}
renderSectionFooter={({ section }) =>
section.title === "Legal" ? (
<Text style={styles.footerNote}>Version 2.4.1</Text>
) : null
}
stickySectionHeadersEnabled
ItemSeparatorComponent={() => <View style={styles.itemSeparator} />}
contentContainerStyle={styles.list}
/>
);
}
const styles = StyleSheet.create({
list: { paddingBottom: 32 },
sectionHeader: {
backgroundColor: "#f8fafc",
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 6,
},
sectionHeaderText: {
fontSize: 12,
fontWeight: "700",
letterSpacing: 0.6,
textTransform: "uppercase",
color: "#64748b",
},
row: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 14,
backgroundColor: "#fff",
},
label: { fontSize: 16, color: "#0f172a" },
value: { fontSize: 15, color: "#64748b" },
itemSeparator: {
height: StyleSheet.hairlineWidth,
backgroundColor: "#e2e8f0",
marginLeft: 16,
},
footerNote: {
textAlign: "center",
fontSize: 12,
color: "#94a3b8",
paddingVertical: 24,
},
});Lo que demuestra:
useMemo en secciones { title, data }.renderSectionHeader renderiza un componente de encabezado ligero por sección.renderSectionFooter añade una nota de versión después de la última sección solamente.stickySectionHeadersEnabled mantiene los títulos de las secciones visibles mientras se desplazan los elementos.ItemSeparatorComponent insertado (vía marginLeft) alinea los divisores con el contenido de la fila, no con el espacio de la sección.SectionList extiende el mismo motor de virtualización que FlatList, pero itera secciones y luego elementos dentro de cada sección.{ data: ItemT[], ...customFields } - comúnmente title, pero puedes adjuntar key, footer o metadatos.renderSectionHeader y renderSectionFooter se llaman para los elementos de la sección; no se virtualizan de la misma manera que las filas (los encabezados pueden fijarse).stickySectionHeadersEnabled (por defecto true en listas de estilo iOS) fija el encabezado de la sección actual en la parte superior hasta que el siguiente encabezado de sección lo desplace.keyExtractor se ejecuta en elementos, no en secciones - la identidad de la sección proviene del índice o un campo key en el objeto de sección.data: []) todavía renderizarán encabezados a menos que las filtres antes de pasar sections.interface TimelineItem {
id: string;
body: string;
at: string;
}
interface TimelineSection {
title: string; // mostrado en el encabezado
key?: string; // clave de sección estable opcional
data: TimelineItem[];
}
// Agrupa elementos planos por etiqueta de fecha
function groupByDate(items: TimelineItem[]): TimelineSection[] {
const map = new Map<string, TimelineItem[]>();
for (const item of items) {
const day = item.at.slice(0, 10); // "2026-07-08"
const bucket = map.get(day) ?? [];
bucket.push(item);
map.set(day, bucket);
}
return Array.from(map.entries()).map(([title, data]) => ({ title, data }));
}| Prop | Renderiza | Contenido típico |
|---|---|---|
renderSectionHeader | Parte superior de cada sección | Letra, fecha, etiqueta de categoría |
renderSectionFooter | Parte inferior de cada sección | Resumen de sección, espaciado, "Ver más" |
ListHeaderComponent | Encima de todas las secciones | Título de pantalla, barra de búsqueda |
ListFooterComponent | Debajo de todas las secciones | Spinner de carga, texto legal |
SectionSeparatorComponent | Entre secciones | Espacio adicional entre grupos |
ItemSeparatorComponent | Entre elementos en una sección | Divisores finos |
stickySectionHeadersEnabled={true} - Comportamiento de estilo iOS Contactos; el encabezado se fija hasta ser reemplazado.stickySectionHeadersEnabled={false} cuando los encabezados son altos (imágenes, gráficos) - los encabezados fijos grandes oscurecen las filas.renderSectionHeader con paddingTop de useSafeAreaInsets() cuando la lista es a pantalla completa.Para saltos de A-Z, empareja SectionList con un raíl de índice posicionado absolutamente que llama a scrollToLocation:
sectionListRef.current?.scrollToLocation({
sectionIndex: 2,
itemIndex: 0,
animated: true,
viewOffset: 0,
});scrollToLocation necesita diseño confiable - proporciona getItemLayout cuando las alturas de las filas son fijas.onScrollToIndexFailed para reintentar después de que se renderizen más filas.import type { SectionListData, SectionListRenderItem } from "react-native";
interface Place {
id: string;
name: string;
}
interface PlaceSection extends SectionListData<Place> {
title: string;
data: Place[];
}
const renderItem: SectionListRenderItem<Place, PlaceSection> = ({ item, section }) => (
<PlaceRow place={item} sectionTitle={section.title} />
);
<SectionList<Place, PlaceSection>
sections={sections}
renderItem={renderItem}
renderSectionHeader={({ section }) => <Header title={section.title} />}
/>SectionList<ItemT, SectionT> cuando las secciones llevan campos personalizados más allá de data.SectionListData<T> es el tipo base para objetos de sección.| Parámetro | Tipo | Descripción |
|---|---|---|
sections | readonly SectionT[] | Array de secciones, cada una con data: ItemT[] |
renderItem | SectionListRenderItem<ItemT, SectionT> | Renderiza un elemento; recibe section en info |
renderSectionHeader | ({ section }) => ReactElement | Encabezado encima de los elementos de cada sección |
renderSectionFooter | ({ section }) => ReactElement | Pie de página debajo de los elementos de cada sección |
keyExtractor | (item: ItemT, index: number) => string | Clave estable por elemento |
stickySectionHeadersEnabled | boolean | Fija encabezados durante el desplazamiento (por defecto true) |
getItemLayout | (data, index) => { length, offset, index } | Altura de elemento fija para scroll-to-location |
Saltos de sección manual dentro de FlatList - Los encabezados falsos como tipos de fila especiales complican la virtualización, el comportamiento fijo y los separadores. Solución: Usa SectionList cuando los datos están agrupados.
Reconstruir secciones en cada renderizado - sections={groupData(items)} asigna nuevos arrays y fuerza una actualización de lista completa. Solución: useMemo(() => groupData(items), [items]).
Claves de elemento duplicadas entre secciones - keyExtractor debe ser globalmente único en todas las secciones, no solo dentro de una. Solución: Prefijar claves: `${section.title}-${item.id}` solo si los ids no son globalmente únicos (prefiere ids globales).
Arrays data vacíos todavía muestran encabezados - Los usuarios ven "Hoy" sin filas debajo. Solución: .filter((s) => s.data.length > 0) antes de pasar sections.
Encabezados de sección pesados - Imágenes grandes o gráficos en renderSectionHeader causan tartamudeo cuando los encabezados fijos se apilan. Solución: Mantén los encabezados compactos; mueve el contenido enriquecido a la primera fila o a un ListHeaderComponent.
scrollToLocation sin getItemLayout - Las filas de altura variable hacen que las matemáticas de índice sean incorrectas y activen onScrollToIndexFailed. Solución: Alturas fijas + getItemLayout, o maneja el callback de fallo con un reintento.
Olvidar extraData para el estado de la fila - Mismo comportamiento PureComponent que FlatList - el estado de selección fuera de sections no actualizará las filas. Solución: Pasa extraData cuando la interfaz de usuario de la fila depende del estado externo.
| Alternativa | Úsalo Cuando | No lo Uses Cuando |
|---|---|---|
SectionList | Grupos etiquetados, encabezados fijos, listas de alfabeto | Secuencia plana única sin elementos de sección |
FlatList + filas de encabezado | Uno o dos saltos visuales, sin encabezados fijos | Muchas secciones con comportamiento fijo de estilo iOS |
FlashList con stickyHeaderIndices | Feeds agrupados críticos en rendimiento | Necesitas una API renderSectionHeader de primera clase |
ScrollView + mapas anidados | Configuraciones estáticas cortas (- 30 filas en total) | Listas largas de contactos - sin virtualización |
groupBy en state | Las secciones raramente cambian | Reagrupación en cada pulsación de tecla sin useMemo |
FlatList virtualiza un array único data. SectionList virtualiza múltiples arrays data agrupados bajo encabezados de sección (y pies de página opcionales). Usa SectionList cuando la agrupación es parte del modelo de datos, no una fila de encabezado única.
Un array de objetos con al menos data: Item[]. Añade title, key o campos personalizados. Ejemplo: [{ title: "A", data: [...] }, { title: "B", data: [...] }].
Solo a elementos. Dale a cada sección una propiedad key si necesitas identidad de sección estable: { key: "account", title: "Account", data: [...] }.
stickySectionHeadersEnabled (por defecto true) mantiene el encabezado de la sección actual fijo en la parte superior de la lista hasta que el encabezado de la siguiente sección se desplace hacia la vista. Desactiva para encabezados altos que cubrirían demasiado contenido.
Ejecuta un reductor groupBy en useMemo - agrupa por fecha, categoría o primera letra. Ordena las claves de sección, luego mapea a { title, data }. No agrupes en línea en JSX.
Sí - usa una unión discriminada para elementos y ramifica en renderItem. Mantén keyExtractor estable entre variantes. Para diseños de fila muy diferentes, considera listas de sección separadas o FlashList con tipos heterogéneos.
Renderiza entre secciones (después del último elemento de la sección N, antes del encabezado de la sección N+1). Úsalo para espacio vertical adicional entre grupos. ItemSeparatorComponent renderiza entre elementos dentro de una sección.
Usa ListHeaderComponent - se desplaza con el contenido y se sitúa encima de la primera sección. Para una barra de búsqueda fija, renderízala fuera de SectionList en un diseño de columna (flex: 1 en la lista).
Encuentra sectionIndex para la letra objetivo, luego llama a ref.scrollToLocation({ sectionIndex, itemIndex: 0 }). Proporciona getItemLayout para alturas de fila fijas y maneja onScrollToIndexFailed.
Úsalo para resúmenes por sección ("3 elementos"), espaciado o CTAs. Para contenido de pie de página de toda la aplicación (versión, cargar más), prefiere ListFooterComponent.
Los ids de elemento deben ser únicos en todas las secciones. Si la API reutiliza ids por sección, prefijar: keyExtractor={(item, index) => `${item.id}-${index}`} - pero los ids estables del servidor se prefieren fuertemente.
Sí - añade elementos a la última sección o añade nuevas secciones en tu manejador de paginación. Pasa onEndReached y protege con una bandera de carga. Ver Infinite Scroll & Pagination.
Se aplican las mismas reglas: renderItem estable, filas memo, extraData, evita separadores anónimos. Los encabezados de sección se re-renderizarán cuando cambie la referencia de sections - memoriza la estructura agrupada.
Sí - React Native implementa encabezados fijos en ambas plataformas. Prueba en dispositivos Android con varios niveles de API; el color de fondo del encabezado debe ser opaco para evitar sangrado de fila.
Ver FlatList Recipes para keyExtractor, separadores y patrones de ListEmptyComponent que se aplican igualmente a elementos de SectionList.
keyExtractor, separadores y ListEmptyComponent compartidos por elementos de secciónScrollView, FlatList y SectionListsectionsSectionList<Item, Section>Versiones de Stack: Esta página fue escrita para React 19.2.3, React Native 0.86.0, y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 16 jul 2026