Noções Básicas de Listas
10 exemplos para você começar com Listas e Rolagem - 7 básicos e 3 intermediários.
Busque em todas as páginas da documentação
10 exemplos para você começar com Listas e Rolagem - 7 básicos e 3 intermediários.
Cada exemplo abaixo usa primitivas de lista integradas do React Native - nenhum pacote extra. Crie um aplicativo Expo SDK 57 TypeScript padrão e substitua App.tsx para executar cada trecho.
npx create-expo-app@latest MyListsApp --template blank-typescript
cd MyListsApp
npx expo startFerramentas: Estes exemplos têm como alvo Expo SDK 57 (
expo~57.0.4), React Native 0.86.0 e React 19.2.3.
ScrollView monta cada filho imediatamente. Isso é aceitável quando o número de itens é pequeno e fixo - telas de configurações, textos legais, passos de onboarding.
import { ScrollView, StyleSheet, Text, View } from "react-native";
const SETTINGS = [
{ id: "profile", label: "Editar perfil" },
{ id: "notifications", label: "Notificações" },
{ id: "privacy", label: "Privacidade" },
{ id: "help", label: "Central de ajuda" },
] as const;
export default function App() {
return (
<ScrollView contentContainerStyle={styles.content}>
<Text style={styles.heading}>Configurações</Text>
{SETTINGS.map((item) => (
<View key={item.id} style={styles.row}>
<Text style={styles.label}>{item.label}</Text>
</View>
))}
</ScrollView>
);
}
const styles = StyleSheet.create({
content: { padding: 16, gap: 8 },
heading: { fontSize: 22, fontWeight: "700", marginBottom: 8 },
row: {
padding: 16,
backgroundColor: "#f3f4f6",
borderRadius: 10,
},
label: { fontSize: 16 },
});ScrollView quando você tiver aproximadamente menos de 20 linhas simples e a lista raramente crescer em tempo de execuçãocontentContainerStyle preenche o conteúdo rolável; a própria view de rolagem ainda precisa de uma altura limitada (flex: 1 em um View pai, se necessário)key - aqui item.id é estávelFlatList - montar centenas de linhas em um ScrollView paralisa a thread JS e estoura a memóriaRelacionado: Armadilhas de Virtualização - por que listas
ScrollViewilimitadas travam dispositivos de baixo custo
FlatList virtualiza linhas: renderiza apenas o que é visível mais um pequeno buffer. Passe data e renderItem - esse é o mínimo viável para uma lista.
import { FlatList, StyleSheet, Text, View } from "react-native";
type Post = { id: string; title: string };
const POSTS: Post[] = Array.from({ length: 200 }, (_, index) => ({
id: String(index + 1),
title: `Post #${index + 1}`,
}));
export default function App() {
return (
<FlatList
data={POSTS}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.title}>{item.title}</Text>
</View>
)}
contentContainerStyle={styles.content}
/>
);
}
const styles = StyleSheet.create({
content: { padding: 16 },
row: {
padding: 16,
marginBottom: 8,
backgroundColor: "#eef2ff",
borderRadius: 10,
},
title: { fontSize: 16, fontWeight: "600" },
});data é um array simples - quando muda, FlatList compara usando keyExtractor e recicla views nativasrenderItem recebe { item, index, separators } - mantenha-o puro e rápido; trabalho pesado pertence a componentes filhos memoizadosView style={{ flex: 1 }} dentro de uma tela com um cabeçalhoFlatList é obrigatórioRelacionado: Receitas de FlatList - separadores,
getItemLayoute polimento de estado vazio
keyExtractor diz ao React Native como corresponder linhas entre atualizações. Chaves instáveis causam cintilação, perda de posição de rolagem e estado de seleção quebrado.
import { FlatList, StyleSheet, Text, View } from "react-native";
type Task = { id: string; label: string; done: boolean };
const TASKS: Task[] = [
{ id: "t1", label: "Enviar estado vazio da lista", done: false },
{ id: "t2", label: "Adicionar pull-to-refresh", done: true },
{ id: "t3", label: "SectionList de Perfil", done: false },
];
export default function App() {
return (
<FlatList
data={TASKS}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.bullet}>{item.done ? "✓" : "○"}</Text>
<Text style={[styles.label, item.done && styles.done]}>{item.label}</Text>
</View>
)}
contentContainerStyle={styles.content}
/>
);
}
const styles = StyleSheet.create({
content: { padding: 16 },
row: { flexDirection: "row", alignItems: "center", gap: 12, paddingVertical: 12 },
bullet: { fontSize: 18, width: 24, textAlign: "center" },
label: { fontSize: 16, flex: 1 },
done: { textDecorationLine: "line-through", color: "#9ca3af" },
});id emitido pelo servidor em vez do índice do array - reordenar, filtrar ou adicionar linhas no início quebra chaves baseadas em índiceMath.random() ou Date.now() por renderização - as chaves devem ser estáveis durante a vida útil da linhakeyExtractor={(item) => String(item.id)}Relacionado: Receitas de FlatList -
getItemLayoutcombina bem com IDs estáveis para rolagem para índice
Quando data é [], FlatList não renderiza nada, a menos que você forneça ListEmptyComponent - o hook padrão para estados de carregamento, erro e zero resultados.
import { useEffect, useState } from "react";
import { ActivityIndicator, FlatList, StyleSheet, Text, View } from "react-native";
type Bookmark = { id: string; title: string };
export default function App() {
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const timer = setTimeout(() => {
setBookmarks([]);
setLoading(false);
}, 1200);
return () => clearTimeout(timer);
}, []);
return (
<FlatList
data={bookmarks}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text>{item.title}</Text>
</View>
)}
ListEmptyComponent={
loading ? (
<ActivityIndicator style={styles.empty} />
) : (
<View style={styles.empty}>
<Text style={styles.emptyTitle}>Ainda não há favoritos</Text>
<Text style={styles.emptyBody}>
Salve artigos do feed e eles aparecerão aqui.
</Text>
</View>
)
}
contentContainerStyle={bookmarks.length === 0 ? styles.emptyContainer : styles.content}
/>
);
}
const styles = StyleSheet.create({
content: { padding: 16 },
emptyContainer: { flexGrow: 1, justifyContent: "center" },
empty: { alignItems: "center", padding: 32 },
emptyTitle: { fontSize: 18, fontWeight: "600", marginBottom: 8 },
emptyBody: { fontSize: 14, color: "#6b7280", textAlign: "center", lineHeight: 20 },
row: { padding: 16, backgroundColor: "#f9fafb", borderRadius: 8, marginBottom: 8 },
});ListEmptyComponent renderiza em vez de renderItem quando data.length === 0 - um componente pode ramificar com base no carregamento vs. vazioflexGrow: 1 a contentContainerStyle quando o estado vazio deve centralizar verticalmente dentro da telaListHeaderComponent - os usuários ainda verão um corpo de lista vazio por baixoRelacionado: Receitas de FlatList - padrões de layout de estado vazio e linhas esqueléticas
Usuários de dispositivos móveis esperam atualização ao ultrapassar o limite de rolagem. Conecte o estado refreshing a onRefresh e passe um RefreshControl - ou use os atalhos de props do FlatList.
import { useCallback, useState } from "react";
import { FlatList, RefreshControl, StyleSheet, Text, View } from "react-native";
type Message = { id: string; body: string };
const INITIAL: Message[] = [
{ id: "1", body: "Bem-vindo à caixa de entrada." },
{ id: "2", body: "Puxe para baixo para buscar novas mensagens." },
];
export default function App() {
const [messages, setMessages] = useState(INITIAL);
const [refreshing, setRefreshing] = useState(false);
const onRefresh = useCallback(async () => {
setRefreshing(true);
await new Promise((resolve) => setTimeout(resolve, 900));
setMessages((prev) => [
{ id: String(Date.now()), body: "Nova mensagem acabou de chegar." },
...prev,
]);
setRefreshing(false);
}, []);
return (
<FlatList
data={messages}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.body}>{item.body}</Text>
</View>
)}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
contentContainerStyle={styles.content}
/>
);
}
const styles = StyleSheet.create({
content: { padding: 16 },
row: {
padding: 16,
marginBottom: 8,
backgroundColor: "#ecfdf5",
borderRadius: 10,
},
body: { fontSize: 15, lineHeight: 22 },
});refreshing deve se tornar true quando o trabalho começar e false quando terminar - deixá-lo preso mostra um spinner perpétuoonRefresh se os usuários fizerem "rubber-band" repetidamente; ignore chamadas sobrepostas com um ref ou retorno antecipadocolors (Android) e tintColor (iOS) em RefreshControl para corresponder à marcaonEndReached, não pull-to-refresh - veja o guia de rolagem infinita para padrões de carregamento maisRelacionado: Rolagem Infinita e Paginação -
onEndReached, cursores e guardas de página duplicada
Separadores pertencem às props da lista, não dentro de cada linha. ItemSeparatorComponent mantém renderItem focado no conteúdo da linha.
import { FlatList, StyleSheet, Text, View } from "react-native";
type City = { id: string; name: string; country: string };
const CITIES: City[] = [
{ id: "1", name: "Tóquio", country: "Japão" },
{ id: "2", name: "Berlim", country: "Alemanha" },
{ id: "3", name: "São Paulo", country: "Brasil" },
{ id: "4", name: "Toronto", country: "Canadá" },
];
function Separator() {
return <View style={styles.separator} />;
}
export default function App() {
return (
<FlatList
data={CITIES}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.country}>{item.country}</Text>
</View>
)}
ItemSeparatorComponent={Separator}
contentContainerStyle={styles.content}
/>
);
}
const styles = StyleSheet.create({
content: { padding: 16, backgroundColor: "#fff", borderRadius: 12 },
row: { paddingVertical: 14 },
name: { fontSize: 16, fontWeight: "600" },
country: { fontSize: 13, color: "#6b7280", marginTop: 2 },
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: "#e5e7eb",
},
});renderItemFlatList também expõe ItemSeparatorComponent via separators.highlight() de renderItem para feedback de toque - útil em seletores de contatoSectionList com renderSectionHeader em vez de simular divisores em um array planoReact.memo se eles alocam gradientes ou SVG em cada reciclagemRelacionado: Receitas de FlatList - separadores embutidos e exceções de borda da última linha
Quando linhas pertencem a grupos nomeados - contatos por letra, tarefas por status - SectionList renderiza seções com cabeçalhos fixos opcionais.
import { SectionList, StyleSheet, Text, View } from "react-native";
type Person = { id: string; name: string };
type Section = { title: string; data: Person[] };
const SECTIONS: Section[] = [
{
title: "Engenharia",
data: [
{ id: "1", name: "Alex Kim" },
{ id: "2", name: "Jordan Lee" },
],
},
{
title: "Design",
data: [
{ id: "3", name: "Sam Rivera" },
{ id: "4", name: "Casey Ng" },
],
},
];
export default function App() {
return (
<SectionList
sections={SECTIONS}
keyExtractor={(item) => item.id}
renderSectionHeader={({ section }) => (
<View style={styles.header}>
<Text style={styles.headerText}>{section.title}</Text>
</View>
)}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.name}>{item.name}</Text>
</View>
)}
stickySectionHeadersEnabled
contentContainerStyle={styles.content}
/>
);
}
const styles = StyleSheet.create({
content: { paddingBottom: 24 },
header: {
backgroundColor: "#f3f4f6",
paddingHorizontal: 16,
paddingVertical: 8,
},
headerText: { fontSize: 13, fontWeight: "700", color: "#4b5563" },
row: {
paddingHorizontal: 16,
paddingVertical: 14,
backgroundColor: "#fff",
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: "#e5e7eb",
},
name: { fontSize: 16 },
});{ title, data: T[] } - data contém as linhas para esse grupo; outros metadados (contagens, badges) podem residir no objeto da seçãorenderSectionHeader recebe { section } - estilize cabeçalhos de forma diferente das linhas para escaneabilidadestickySectionHeadersEnabled mantém o rótulo da seção atual fixo durante a rolagem - padrão para contatos e calendáriossectionsRelacionado: SectionList e Dados Agrupados - rodapés, seções de múltiplas colunas e ajuste de cabeçalho fixo
Elementos visuais da tela que rolam com a lista - títulos, filtros, dicas de carregamento mais - pertencem aos slots de cabeçalho/rodapé, não dentro de cada linha.
import { FlatList, StyleSheet, Text, View } from "react-native";
type Item = { id: string; label: string };
const DATA: Item[] = [
{ id: "1", label: "Auditar chaves da lista" },
{ id: "2", label: "Medir engasgo da rolagem" },
{ id: "3", label: "Enviar carregador esquelético" },
];
function ListHeader() {
return (
<View style={styles.header}>
<Text style={styles.title}>Backlog da Sprint</Text>
<Text style={styles.subtitle}>3 itens · deslize para baixo em um feed para atualizar</Text>
</View>
);
}
function ListFooter() {
return (
<Text style={styles.footer}>Fim do backlog - o rodapé rola com o conteúdo.</Text>
);
}
export default function App() {
return (
<FlatList
data={DATA}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.row}>
<Text>{item.label}</Text>
</View>
)}
ListHeaderComponent={ListHeader}
ListFooterComponent={ListFooter}
contentContainerStyle={styles.content}
/>
);
}
const styles = StyleSheet.create({
content: { padding: 16 },
header: { marginBottom: 16 },
title: { fontSize: 22, fontWeight: "700" },
subtitle: { fontSize: 14, color: "#6b7280", marginTop: 4 },
row: {
padding: 14,
backgroundColor: "#f9fafb",
borderRadius: 8,
marginBottom: 8,
},
footer: {
textAlign: "center",
color: "#9ca3af",
fontSize: 13,
marginTop: 8,
marginBottom: 24,
},
});ListFooterComponent é o local idiomático para "carregando mais" spinners ao paginar (não ListEmptyComponent)View fixo no topo, FlatList com flex: 1 abaixoRelacionado: Rolagem Infinita e Paginação - carregadores de rodapé e
onEndReachedThreshold
Carrosséis, chips de tags e tiras de miniaturas usam a mesma API FlatList com horizontal - ainda virtualizados ao longo do eixo cruzado.
import { FlatList, Pressable, StyleSheet, Text, View } from "react-native";
import { useState } from "react";
const TAGS = [
{ id: "all", label: "Todos" },
{ id: "design", label: "Design" },
{ id: "mobile", label: "Mobile" },
{ id: "perf", label: "Performance" },
{ id: "a11y", label: "Acessibilidade" },
] as const;
export default function App() {
const [active, setActive] = useState<string>("all");
return (
<View style={styles.screen}>
<Text style={styles.heading}>Tópicos</Text>
<FlatList
data={TAGS}
horizontal
showsHorizontalScrollIndicator={false}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
const selected = item.id === active;
return (
<Pressable
onPress={() => setActive(item.id)}
style={[styles.chip, selected && styles.chipSelected]}
>
<Text style={[styles.chipText, selected && styles.chipTextSelected]}>
{item.label}
</Text>
</Pressable>
);
}}
contentContainerStyle={styles.strip}
/>
<Text style={styles.caption}>Selecionado: {active}</Text>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, paddingTop: 24 },
heading: { fontSize: 18, fontWeight: "600", paddingHorizontal: 16, marginBottom: 12 },
strip: { paddingHorizontal: 16, gap: 8 },
chip: {
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 999,
backgroundColor: "#e5e7eb",
},
chipSelected: { backgroundColor: "#2563eb" },
chipText: { fontSize: 14, color: "#374151" },
chipTextSelected: { color: "#fff", fontWeight: "600" },
caption: { padding: 16, color: "#6b7280" },
});horizontal inverte o eixo de rolagem - keyExtractor e renderItem permanecem os mesmosshowsHorizontalScrollIndicator={false} para trilhas de chip; mantenha true para carrosséis de imagem onde a indicação ajudagap em contentContainerStyle (RN 0.71+) ou margem nos itens para espaçamento - evite hacks de preenchimento no último chipFlatList vertical precisam de configuração cuidadosa de gestos - veja o guia de scrollables aninhados antes de enviar feeds com carrosséis inlineRelacionado: Scrollables Aninhados - conflitos de gestos verticais + horizontais
FlatList memoiza linhas contra data. Quando a aparência da linha depende de estado externo (seleção, tema, ID expandido), passe extraData para que as linhas visíveis sejam atualizadas.
import { useState } from "react";
import { FlatList, Pressable, StyleSheet, Text, View } from "react-native";
type File = { id: string; name: string };
const FILES: File[] = [
{ id: "a", name: "Relatório-trimestral.pdf" },
{ id: "b", name: "Lista-de-verificação-de-lançamento.md" },
{ id: "c", name: "Ícones-do-aplicativo.sketch" },
];
export default function App() {
const [selectedId, setSelectedId] = useState<string | null>(null);
return (
<FlatList
data={FILES}
extraData={selectedId}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
const selected = item.id === selectedId;
return (
<Pressable
onPress={() => setSelectedId(item.id)}
style={[styles.row, selected && styles.rowSelected]}
>
<Text style={[styles.name, selected && styles.nameSelected]}>
{item.name}
</Text>
</Pressable>
);
}}
contentContainerStyle={styles.content}
/>
);
}
const styles = StyleSheet.create({
content: { padding: 16 },
row: {
padding: 16,
borderRadius: 10,
backgroundColor: "#f9fafb",
marginBottom: 8,
borderWidth: 2,
borderColor: "transparent",
},
rowSelected: { borderColor: "#2563eb", backgroundColor: "#eff6ff" },
name: { fontSize: 15 },
nameSelected: { fontWeight: "600", color: "#1d4ed8" },
});extraData, alternar selectedId pode não re-renderizar linhas visíveis porque a referência do array data não mudaselectedId), um objeto ou um array - o que quer que seu renderItem leia de fora do itemSet ou Record no estado e passe-o como extraData, ou incorpore flags diretamente em data quando você controlar o modeloReact.memo em componentes de linha extraídos e um callback renderItem estável para listas maiores - veja a otimização de desempenho para windowSize e maxToRenderPerBatchRelacionado: Otimização de Desempenho de Listas -
windowSize, linhas memoizadas e tamanhos de lote | FlashList vs FlatList - quando atualizar bibliotecas de virtualização
Versões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026