Props, State & Re-renders on Mobile
Como as atualizações se propagam nos threads nativos vs. o runtime JS.
Busque em todas as páginas da documentação
Como as atualizações se propagam nos threads nativos vs. o runtime JS.
Cartão de receita de referência rápida - pronto para copiar e colar.
import { useCallback, useState } from "react";
import { FlatList, Pressable, StyleSheet, Text, View } from "react-native";
interface CounterProps {
label: string;
initialCount?: number;
}
export function Counter({ label, initialCount = 0 }: CounterProps) {
const [count, setCount] = useState(initialCount);
const increment = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
return (
<View style={styles.row}>
<Text style={styles.label}>{label}</Text>
<Text style={styles.value}>{count}</Text>
<Pressable onPress={increment} style={styles.button}>
<Text style={styles.buttonText}>+1</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
row: { flexDirection: "row", alignItems: "center", gap: 12, padding: 12 },
label: { flex: 1, fontSize: 16 },
value: { fontSize: 18, fontWeight: "700", minWidth: 32, textAlign: "right" },
button: { backgroundColor: "#2563eb", paddingHorizontal: 14, paddingVertical: 8, borderRadius: 8 },
buttonText: { color: "#fff", fontWeight: "600" },
});Quando usar isso: Qualquer tela interativa onde props pai configuram filhos e useState local controla a UI que deve permanecer sincronizada com as views nativas.
import { memo, useCallback, useMemo, useState } from "react";
import {
ActivityIndicator,
FlatList,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
interface Todo {
id: string;
title: string;
done: boolean;
}
interface TodoRowProps {
item: Todo;
onToggle: (id: string) => void;
}
const TodoRow = memo(function TodoRow({ item, onToggle }: TodoRowProps) {
return (
<Pressable
onPress={() => onToggle(item.id)}
style={[styles.row, item.done && styles.rowDone]}
>
<Text style={[styles.title, item.done && styles.titleDone]}>{item.title}</Text>
<Text style={styles.badge}>{item.done ? "Done" : "Open"}</Text>
</Pressable>
);
});
const SEED: Todo[] = [
{ id: "1", title: "Profile layout with View + Text", done: true },
{ id: "2", title: "Wire Pressable handlers", done: false },
{ id: "3", title: "Measure list scroll perf", done: false },
];
export default function TodoScreen() {
const [todos, setTodos] = useState<Todo[]>(SEED);
const [filter, setFilter] = useState<"all" | "open">("all");
const [loading, setLoading] = useState(false);
const visible = useMemo(
() => (filter === "open" ? todos.filter((t) => !t.done) : todos),
[todos, filter]
);
const openCount = useMemo(() => todos.filter((t) => !t.done).length, [todos]);
const handleToggle = useCallback((id: string) => {
setTodos((prev) =>
prev.map((t) => (t.id === id ? { ...t, done: !t.done } : t))
);
}, []);
const handleRefresh = useCallback(async () => {
setLoading(true);
await new Promise((r) => setTimeout(r, 400));
setTodos(SEED);
setLoading(false);
}, []);
return (
<View style={styles.screen}>
<View style={styles.header}>
<Text style={styles.heading}>Todos ({openCount} open)</Text>
<View style={styles.filters}>
{(["all", "open"] as const).map((key) => (
<Pressable
key={key}
onPress={() => setFilter(key)}
style={[styles.chip, filter === key && styles.chipActive]}
>
<Text style={[styles.chipText, filter === key && styles.chipTextActive]}>
{key === "all" ? "All" : "Open"}
</Text>
</Pressable>
))}
</View>
</View>
{loading ? (
<ActivityIndicator style={styles.loader} />
) : (
<FlatList
data={visible}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <TodoRow item={item} onToggle={handleToggle} />}
ItemSeparatorComponent={() => <View style={styles.separator} />}
contentContainerStyle={styles.list}
/>
)}
<Pressable onPress={handleRefresh} style={styles.refresh}>
<Text style={styles.refreshText}>Reset list</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, backgroundColor: "#f8fafc" },
header: { padding: 16, gap: 12 },
heading: { fontSize: 22, fontWeight: "700", color: "#0f172a" },
filters: { flexDirection: "row", gap: 8 },
chip: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 999, backgroundColor: "#e2e8f0" },
chipActive: { backgroundColor: "#2563eb" },
chipText: { fontSize: 14, fontWeight: "600", color: "#334155" },
chipTextActive: { color: "#fff" },
list: { paddingHorizontal: 16, paddingBottom: 24 },
row: {
flexDirection: "row",
alignItems: "center",
backgroundColor: "#fff",
padding: 14,
borderRadius: 10,
gap: 8,
},
rowDone: { opacity: 0.7 },
title: { flex: 1, fontSize: 16, color: "#0f172a" },
titleDone: { textDecorationLine: "line-through", color: "#64748b" },
badge: { fontSize: 12, fontWeight: "600", color: "#2563eb" },
separator: { height: 8 },
loader: { marginTop: 32 },
refresh: { margin: 16, padding: 14, borderRadius: 10, backgroundColor: "#0f172a" },
refreshText: { color: "#fff", textAlign: "center", fontWeight: "600" },
});O que isso demonstra:
item, onToggle) fluem para uma linha memoizada enquanto o estado (todos, filter) reside na tela.setState evitam closures obsoletos ao alternar itens.useMemo deriva dados filtrados e contagem de abertos sem re-renders extras apenas por recomputação.useCallback mantém handleToggle estável para que memo em TodoRow possa pular linhas inalteradas.FlatList re-renderiza apenas as linhas visíveis - o estado pai ainda aciona a reconciliação para essas linhas.useState e useReducer agendam um re-render quando o atualizador é executado; o valor de retorno é o snapshot de renderização atual.setState em manipuladores de eventos, promessas e timeouts em um único render.Toque do usuário (thread da UI)
→ evento enviado para o thread JS
→ onPress / handler é executado
→ setState agenda o render
→ função do componente é re-executada (JS)
→ React compara a árvore virtual (JS)
→ Fabric commita os nós de sombra alterados
→ layout e pintura nativos (thread da UI)
| Padrão | API | Nota para Mobile |
|---|---|---|
| Estado local da UI | useState | Campos de formulário, toggles, estado de abertura de sheets |
| Dados derivados | useMemo | Listas filtradas - mais barato do que armazenar estado duplicado |
| Handlers estáveis | useCallback | Necessário para otimização de linha memo/FlatList |
| Refs mutáveis | useRef | Timers, handles de animação - sem re-render na alteração de .current |
| Estado compartilhado da tela | Context / store | Prefira estado colocalizado até que várias abas precisem dele |
| Dados do servidor | TanStack Query / use | Mantenha o fetching fora do render; mostre skeletons enquanto pendente |
| Situação | Usar props | Usar estado |
|---|---|---|
| Valor de propriedade do pai | ✓ | |
| Valor muda apenas dentro do componente | ✓ | |
| Componentes irmãos precisam do mesmo valor | Elevar para o pai ✓ | |
| Valor de parâmetros de navegação | Prop de rota ✓ | Copiar para estado se editável |
| Carga útil da API remota | Passar para baixo após o fetch | Armazenar no cache de consulta |
import type { Dispatch, SetStateAction } from "react";
interface ScreenProps {
userId: string;
onSignOut: () => void;
}
// Tipo de setter explícito para callbacks que encaminham setState
type SetTodos = Dispatch<SetStateAction<Todo[]>>;
// Uniões discriminadas para UI assíncrona - evita estados impossíveis de loading+error
type LoadState =
| { status: "idle" }
| { status: "loading" }
| { status: "error"; message: string }
| { status: "success"; data: Todo[] };interface ou type; exporte props de linha ao usar memo.SetStateAction<T> cobre ambas as formas de valor e função atualizadora.Callbacks inline instáveis em listas - onPress={() => toggle(item.id)} dentro de renderItem quebra memo a cada renderização. Correção: Passe id para uma linha memoizada que chama um handler useCallback estável.
Armazenar dados derivados em estado - Manter openTodos em estado ao lado de todos duplica atualizações e arrisca desvios. Correção: Derive com useMemo da fonte de verdade.
Mutar objetos de estado - todo.done = true então setTodos(todos) pode pular a re-renderização porque a referência não mudou. Correção: Retorne novos objetos/arrays dos atualizadores.
Assumir pintura nativa imediata após setState - JS termina o passe de renderização antes que o nativo commite; rajadas rápidas ainda enfileiram trabalho. Correção: Agrupe atualizações relacionadas; evite leituras de layout síncronas após cada tick.
Valor de contexto recriado a cada renderização - value={{ user, theme }} re-renderiza todos os consumidores. Correção: Memoize o objeto de valor de contexto ou divida os contextos.
Memoizar tudo em excesso - useMemo/useCallback têm seu próprio custo e ruído. Correção: Perfilar primeiro; otimizar linhas de lista e computações puras pesadas.
Ler estado logo após setState - setCount(1); console.log(count) ainda registra o snapshot antigo. Correção: Use a forma de atualizador ou useEffect com chave no valor.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
useState local | UI com escopo de tela que não cruza rotas | Muitos primos distantes precisam dos mesmos dados |
| React Context | Tema, sessão de autenticação, flags de recursos lidos amplamente | Atualizações de alta frequência (posição de scroll, teclas digitadas) |
| Zustand / Jotai | Estado de aplicativo médio com boilerplate mínimo | Um callback pai-filho é suficiente |
| TanStack Query | Listas de servidor, cache, atualização em segundo plano | Simplesmente toggle local sem rede |
useRef para valores mutáveis | Frames de animação, intervalos, handles imperativos | Valores que devem aparecer na tela |
| Inputs não controlados + ref | Formulários simples com poucos campos | Validação complexa entre etapas |
memo).setTimeout, resoluções de fetch e eventos nativos são agrupados por padrão.setState no mesmo tick produzem uma re-renderização.flushSync apenas quando você precisar intencionalmente de uma pintura síncrona (raro no mobile).renderItem a menos que seja memoizado; nova identidade de função pode derrotar a memoização da linha.memo na linha, callbacks useCallback estáveis e extraData apenas quando necessário.setState / useReducer.style={{ flex: 1 }} inline cria um novo objeto a cada vez.useCallback e eleve estilos para StyleSheet.create.useRef armazena valores mutáveis que não devem acionar re-renders quando atualizados.useState.FlatList é um PureComponent - ele pode pular renderItem se a referência data não for alterada.extraData={selectedId} quando a aparência da linha depender de estado fora de data.// Errado - cada chamada usa o mesmo snapshot
setCount(count + 1);
setCount(count + 1);
// Certo - atualizador funcional encadeia no último valor
setCount((c) => c + 1);
setCount((c) => c + 1);count no closure está obsoleta dentro do mesmo handler.memo evite com props iguais.children muda se o pai definir inline JSX que captura novos closures.expo-router e React Navigation passam parâmetros de rota como props ou valores de hook (useLocalSearchParams).useEffect, não ao corpo do render.setTodos((prev) => ...) para não fechar sobre todos.id em vez de capturar variáveis de loop quando possível.useMemo apenas armazena um valor computado entre renders do mesmo componente.memo em filhos ao passar objetos ou arrays memoizados para baixo.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: 16 de jul. de 2026