Consumo de APIs no React Native

Do Problema ao Padrão Ouro

Módulo Completo: Conceitos, TanStack Query, Mutations e Arquitetura.

Parte 1: Conceitos Fundamentais

O que realmente acontece por trás dos panos?

O que é uma API?

Pense na API como um garçom em um restaurante.

  1. Você (App): Faz o pedido (Request).
  2. Garçom (API): Leva o pedido à cozinha (Servidor).
  3. Cozinha (Banco de Dados): Prepara a comida.
  4. Garçom (API): Traz a comida até você (Response).

No React Native, usamos o fetch ou axios para conversar com esse "garçom".

O Ciclo de Vida da Requisição

Quando seu app pede dados da internet, três estados principais podem acontecer:

  1. Loading (Carregando):

    • A requisição saiu, mas a resposta ainda não voltou.
    • Interface: Mostramos um spinner ou esqueleto de tela.
  2. Success (Sucesso):

    • Os dados chegaram e estão prontos para uso.
    • Interface: Mostramos a lista de itens.
  3. Error (Erro):

    • A internet caiu ou o servidor deu problema.
    • Interface: Mostramos uma mensagem de erro e um botão de "Tentar Novamente".

Parte 2: O Jeito Antigo (e problemático)

Por que usar useEffect para buscar dados é ruim?

A Abordagem "Ingênua" com useEffect

A maioria dos tutoriais básicos ensina assim:

useEffect(() => {
  setIsLoading(true); // 1. Gerenciar estado manualmente
  
  fetch('https://api.exemplo.com/dados')
    .then(res => res.json())
    .then(data => {
      setData(data);    // 2. Salvar dados manualmente
      setIsLoading(false);
    })
    .catch(err => {
      setError(err);    // 3. Tratar erro manualmente
      setIsLoading(false);
    });
}, []);

Parece simples, certo? Mas há armadilhas perigosas...

O Problema 1: Boilerplate e Complexidade

Para fazer uma única requisição, você precisa:

  • Criar 3 estados (useState): data, isLoading, error.
  • Escrever a lógica de fetch dentro do useEffect.
  • Lembra de limpar o efeito se o componente desmontar? (Muitos esquecem!).
  • Lembra de tratar o erro de conversão de JSON?

Resultado: Código verboso, repetitivo e propenso a bugs.

O Problema 2: Ausência de Cache

Imagine que o usuário:

  1. Abre a tela de Perfil (carrega os dados).
  2. Vai para a tela de Configurações.
  3. Volta para a tela de Perfil.

Com useEffect: A API é chamada novamente.

  • Desperdício de dados móveis do usuário.
  • Interface lenta (mostra loading toda vez).
  • Experiência de usuário ruim.

O Problema 3: Condição de Corrida (Race Condition)

O que acontece se o usuário clicar muito rápido?

  1. Clica no "Post 1" (Requisição A sai).
  2. Clica no "Post 2" (Requisição B sai).
  3. A Requisição B chega antes da A. A tela mostra o Post 2.
  4. A Requisição A chega depois (atrasada). A tela atualiza para o Post 1.

Resultado: O usuário clicou no Post 2, mas a tela mostra o Post 1. Bug grave!

O Problema 4: Sem Retentativas (Retry)

Se a internet oscilar por 1 segundo e a requisição falhar:

  • Com useEffect: O usuário vê uma tela de erro. Ele precisa recarregar o app manualmente.
  • Ideal: O app deveria tentar novamente automaticamente.

A Solução: TanStack Query

(Antigo React Query)

O que é o TanStack Query?

É uma biblioteca de Gerenciamento de Estado do Servidor (Server State).

Ela assume a responsabilidade de:

  • Buscar dados.
  • Armazenar em Cache.
  • Atualizar dados.
  • Gerenciar erros e retentativas.

Você escreve menos código e ganha mais funcionalidades.

Comparativo Visual

Funcionalidade useEffect Manual TanStack Query
Código Muito (Verboso) Pouco (Declarativo)
Cache ❌ Não existe ✅ Automático
Loading/Error Manual (useState) Automático
Retry Manual (Complexo) ✅ Automático (padrão 3x)
Race Condition Risco de Bug ✅ Resolve automaticamente

Parte 3: Passo a Passo

Configurando o TanStack Query no React Native

Passo 1: Instalação

Abra seu terminal na pasta do projeto e execute:

npm install @tanstack/react-query

Passo 2: Configurar o Provider

O TanStack Query precisa de um "Contexto Global" para armazenar o cache.
No seu arquivo raiz (App.js ou _layout.js):

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

// Cria o cliente (fora do componente!)
const queryClient = new QueryClient();

export default function App() {
  return (
    // Envolva todo o app com o Provider
    <QueryClientProvider client={queryClient}>
      <SeuAppAqui />
    </QueryClientProvider>
  );
}

Passo 3: Usando o useQuery (GET)

Agora, no seu componente, o mágico acontece:

import { useQuery } from '@tanstack/react-query';

export function ListaUsuarios() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['usuarios'],      // ID único para o cache
    queryFn: () => 
      fetch('https://jsonplaceholder.typicode.com/users')
        .then(res => res.json()),
  });

  if (isLoading) return <Text>Carregando...</Text>;
  if (error) return <Text>Erro: {error.message}</Text>;

  return (
    <FlatList data={data} renderItem={({item}) => <Text>{item.name}</Text>} />
  );
}

Entendendo as Opções Principais

  1. queryKey (Chave da Query):

    • É como um ID para os dados.
    • Ex: ['usuarios'], ['usuario', 1], ['posts', 'top10'].
    • O Query usa isso para salvar no cache. Se você pedir ['usuarios'] de novo, ele devolve instantaneamente.
  2. queryFn (Função da Query):

    • A função que retorna uma Promise (o fetch).

Parte 4: Mutations

Enviando dados para a API (POST/PUT/DELETE)

Query vs. Mutation

Até agora usamos o useQuery apenas para ler dados (GET).

Mas e se quisermos criar um novo usuário ou excluir um post?

  • Query (Pergunta): "Me dê a lista de posts." (Não altera o servidor).
  • Mutation (Mudança): "Crie este novo post." (Altera o servidor).

Para mutations, usamos o hook useMutation.

Exemplo Prático: Criando um Post

Diferente do useQuery, a mutation não executa automaticamente. Ela retorna uma função mutate.

import { useMutation, useQueryClient } from '@tanstack/react-query';

function FormularioPost() {
  const queryClient = useQueryClient(); // Acesso ao cache global

  const { mutate } = useMutation({
    mutationFn: (novoPost) => fetch('https://.../posts', {
        method: 'POST',
        body: JSON.stringify(novoPost),
    }).then(res => res.json()),
    
    onSuccess: () => {
      // MAGIA: Invalida o cache da query 'posts'
      // Isso faz com que o useQuery dispare novamente automaticamente!
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });

  return <Button title="Salvar" onPress={() => mutate({ title: 'Novo' })} />;
}

Parte 5: Isolamento e Custom Hooks

Deixando o componente limpo

O Problema do Código "Sujo"

Conforme seu app cresce, seu componente começa a ficar cheio de lógica de negócio:

// Componente muito poluído
export function UserList() {
  const { data, ... } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('...').then(r => r.json()),
    // ... configs
  });

  const { mutate } = useMutation({ ... });

  if (isLoading) return ...
  // ... renderização
}

O componente deve se preocupar com COMO EXIBIR, não com COMO BUSCAR.

A Solução: Custom Hooks

Vamos criar um hook separado para encapsular toda a lógica.

Arquivo: src/hooks/useUsers.js

import { useQuery } from '@tanstack/react-query';

// Função de busca isolada
const fetchUsers = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  return res.json();
};

export const useUsers = () => {
  // Toda a lógica complexa fica aqui dentro
  return useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });
};

O Componente Limpo

Agora veja como o componente fica simples e legível:

Arquivo: src/screens/UserList.js

import { useUsers } from '../hooks/useUsers'; // Importando nosso hook

export function UserList() {
  // Apenas chamamos o hook customizado
  const { data: users, isLoading, error } = useUsers();

  if (isLoading) return <ActivityIndicator />;
  if (error) return <Text>Erro!</Text>;

  return (
    <FlatList
      data={users}
      renderItem={({ item }) => <Text>{item.name}</Text>}
    />
  );
}

Vantagens do Isolamento

  1. Reutilização: Se você precisar da lista de usuários em outra tela, basta chamar useUsers(). O cache é compartilhado.
  2. Manutenção: Se a URL da API mudar, você altera apenas no arquivo do hook.
  3. Testes: É muito mais fácil testar um hook isolado do que um componente inteiro com UI.

Resumo Final da Aula

  1. Conceito: APIs são a ponte entre o App e o Servidor.
  2. Problema: O useEffect manual gera bugs (Race Condition), código sujo e não tem cache.
  3. Solução: TanStack Query gerencia cache, loading e erros automaticamente.
  4. Queries (useQuery): Para buscar dados (GET).
  5. Mutations (useMutation): Para alterar dados (POST/PUT/DELETE).
    • Lembre-se de usar invalidateQueries para atualizar a lista.
  6. Arquitetura: Use Custom Hooks para separar a lógica de dados da interface.

Fim da Aula

Agora é hora de codar!