Otimizando a busca por nome no app: como evitamos carregar “lista gigante” e travar o React Native
Em muitos apps, principalmente os que lidam com cadastro de clientes/associados, existe uma tentação comum: abrir a tela de “selecionar cliente” e já trazer todos os registros para facilitar. O problema é que, quando a base cresce, essa decisão vira um “gerador de travamento”.
Neste artigo, vou mostrar um caso real e bem frequente: a tela travava porque carregava uma lista enorme logo ao abrir — e como a solução de buscar apenas após o usuário digitar (search-on-demand) deixou o app mais rápido, mais leve e mais intuitivo.
O problema: “carregar tudo” parece simples… até não ser
Quando a tela abre e faz um GET /clientes retornando milhares de itens, a cadeia de problemas aparece rápido:
- Tempo de rede maior (payload grande)
- JS thread ocupada processando e renderizando muitos itens
- Memória aumenta (principalmente se o item tem dados extras)
- FlatList ainda sofre se a renderização não estiver bem configurada
- UX ruim: o usuário não quer “ver todos”, quer encontrar 1
Em dispositivos mais simples, isso vira:
- engasgos ao rolar
- tela “congelando”
- travamento ao digitar (porque o app já está ocupado)
A virada: buscar somente após digitar (search-on-demand)
A mudança é simples no conceito e enorme no impacto:
✅ Antes: ao abrir, carregava lista completa
✅ Agora: a tela inicia em branco e só consulta o backend após o usuário digitar e pesquisar
Isso traz alguns ganhos imediatos:
- reduz carga inicial (tempo de abertura da tela)
- reduz dados trafegados
- melhora a responsividade (menos render desnecessário)
- deixa a UX mais “óbvia”: “digite para buscar”
O padrão recomendado de UX
Uma tela de busca bem esperada pelo usuário costuma seguir isso:
- Campo de busca com placeholder: “Digite o nome…”
- Sem resultados inicialmente (estado vazio)
- Ao digitar:
- mostra “buscando…” (loading)
- exibe resultados
- Se o campo estiver vazio:
- limpa resultados e volta ao estado vazio
Dica: o “estado vazio” não precisa ser “nada”. Pode ser um bloco com ícone + texto “Digite um nome para buscar”.
Implementação prática (React Native)
1) Estados básicos
Você precisa de:
query(texto digitado)results(lista)loadinghasSearched(opcional, para diferenciar “ainda não buscou” vs “buscou e não achou”)
Exemplo (simplificado):
const [query, setQuery] = useState('');
const [results, setResults] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [hasSearched, setHasSearched] = useState(false);
2) Só buscar se houver texto mínimo
Defina um mínimo pra evitar buscas “A”, “B”:
const MIN_CHARS = 2;
async function searchByName() {
const q = query.trim();
if (q.length < MIN_CHARS) {
setResults([]);
setHasSearched(false);
return;
}
setLoading(true);
setHasSearched(true);
try {
const resp = await httpGet(`/clientes?nome=${encodeURIComponent(q)}`);
setResults(resp?.data ?? []);
} catch (e) {
setResults([]);
} finally {
setLoading(false);
}
}
3) Buscar ao clicar (ou ao “submit” do teclado)
- Botão “Buscar”
onSubmitEditingno input (tecla Enter/Buscar do teclado)
<TextInput
value={query}
onChangeText={setQuery}
placeholder="Digite o nome do cliente..."
returnKeyType="search"
onSubmitEditing={searchByName}
/>
<TouchableOpacity onPress={searchByName}>
<Text>Buscar</Text>
</TouchableOpacity>
Melhorando ainda mais: debounce (sem spammar o backend)
Se você quiser buscar enquanto digita, use debounce (ex.: 400–600ms). Assim evita 10 requisições por segundo.
Sem biblioteca, uma abordagem simples:
useEffect(() => {
const q = query.trim();
if (q.length < MIN_CHARS) {
setResults([]);
setHasSearched(false);
return;
}
const t = setTimeout(() => {
searchByName();
}, 500);
return () => clearTimeout(t);
}, [query]);
Atenção: se você já busca no useEffect, evite chamar também no botão sem necessidade, ou use o botão como “forçar busca” apenas.Backend: a outra metade da performance
Não adianta otimizar o front se o endpoint é pesado.
Checklist no backend:
- implementar busca por nome no banco (
LIKE,ILIKE, full-text, etc.) - retornar só campos necessários (id, nome, cpf parcial, etc.)
- paginar:
limiteoffset/ cursor - ordenar por relevância (quando fizer sentido)
Exemplo de contrato de endpoint ideal:
GET /clientes?nome=ana&limit=20&offset=0
FlatList: deixe ela trabalhar a seu favor
Mesmo com poucos resultados, vale configurar bem:
keyExtractorestávelkeyboardShouldPersistTaps="handled"ListEmptyComponentpara estado vazioinitialNumToRenderewindowSize(se precisar)
<FlatList
data={results}
keyExtractor={(item) => String(item.id)}
keyboardShouldPersistTaps="handled"
renderItem={({ item }) => <ClienteRow item={item} />}
ListEmptyComponent={
loading ? null : (
<Text>
{hasSearched ? 'Nenhum cliente encontrado.' : 'Digite um nome para buscar.'}
</Text>
)
}
/>
Resultado: menos carga, mais performance, melhor UX
Depois de migrar para “buscar só após digitar”, o que normalmente melhora:
- tempo de abertura da tela
- fluidez geral (menos travamento)
- menos consumo de dados e memória
- experiência mais clara para o usuário
O mais interessante é que esse tipo de melhoria quase sempre é:
- rápida de implementar
- fácil de medir (tempo, memória, logs)
- extremamente perceptível na mão do usuário
Checklist rápido (pra aplicar hoje)
- A tela abre sem carregar lista completa
- Só busca após
MIN_CHARS - Debounce se buscar enquanto digita
- Endpoint paginado e enxuto
-
FlatListcom estados de vazio e loading - Limpar resultados ao apagar texto
Performance não é só “micro-otimização”; muitas vezes é tomar a decisão certa de fluxo: não fazer trabalho que o usuário não pediu.
No caso de busca, “carregar tudo” é o atalho que cobra caro depois.
Se você quiser, eu também posso:
- adaptar esse artigo para o seu caso (com endpoint/rotas reais),
- incluir prints da tela “antes/depois”,
- ou montar uma versão com métricas (tempo de abertura, tamanho do payload, etc.).


