Vis e3o geral: o que significa “consumir APIs” em um app React Native
Consumir uma API e9 transformar requisi e7 f5es HTTP (GET, POST, PUT, DELETE etc.) em dados utiliz e1veis no app. Em apps reais, isso envolve mais do que “fazer um fetch”: voc ea precisa lidar com autentica e7 e3o, timeouts, cancelamento, padroniza e7 e3o de erros, retries, logs e estados de carregamento para manter a experi eancia consistente e diagn f3sticos poss edveis.
Neste cap edtulo, voc ea vai montar uma camada de rede reutiliz e1vel, com duas abordagens: fetch (nativo) e Axios (biblioteca), e depois evoluir para autentica e7 e3o com token, refresh e prote e7 e3o de rotas.
HTTP na pr e1tica: fetch vs Axios
Quando usar fetch
- Sem depend eancias externas.
- Bom para apps simples ou quando voc ea quer controle total.
- Voc ea precisa implementar manualmente: timeout, intercepta e7 e3o, padroniza e7 e3o de erros e retries.
Quando usar Axios
- Interceptors para request/response.
- Timeout nativo.
- Cancelamento e integra e7 e3o com AbortController.
- Transforma e7 e3o de resposta e headers mais pr e1ticos.
Passo a passo: criando um client HTTP reutiliz e1vel
O objetivo e9 ter um fanico ponto para: baseURL, headers, token, tratamento de erros, logs e retry. Assim, suas telas chamam fun e7 f5es de servi e7o (ex.: authService.login, userService.me) sem duplicar l f3gica.
1) Defina tipos de resposta e erro padronizados
Padronizar evita que cada tela trate erros de um jeito. Um formato comum e9: sucesso retorna { ok: true, data }, falha retorna { ok: false, error }.
export type ApiSuccess<T> = { ok: true; data: T; status: number };export type ApiFailure = { ok: false; status?: number; error: { code: string; message: string; details?: unknown; isNetworkError?: boolean; isTimeout?: boolean; isAuthError?: boolean; };};export type ApiResult<T> = ApiSuccess<T> | ApiFailure;Exemplos de code: NETWORK_ERROR, TIMEOUT, UNAUTHORIZED, VALIDATION_ERROR, UNKNOWN.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
2) Implementa e7 e3o com fetch (com timeout e cancelamento)
O fetch n e3o tem timeout por padr e3o. Use AbortController para cancelar por timeout e tamb e9m quando a tela desmontar.
const BASE_URL = 'https://api.seudominio.com';type FetchOptions = RequestInit & { timeoutMs?: number };function buildUrl(path: string) { return path.startsWith('http') ? path : `${BASE_URL}${path}`;}async function fetchJson<T>(path: string, options: FetchOptions = {}): Promise<ApiResult<T>> { const { timeoutMs = 15000, ...init } = options; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); try { const res = await fetch(buildUrl(path), { ...init, signal: controller.signal, headers: { 'Content-Type': 'application/json', ...(init.headers || {}), }, }); const status = res.status; const contentType = res.headers.get('content-type') || ''; const isJson = contentType.includes('application/json'); const body = isJson ? await res.json().catch(() => null) : await res.text().catch(() => null); if (!res.ok) { const message = (body && (body.message || body.error)) || 'N e3o foi poss edvel completar a requisi e7 e3o.'; const code = status === 401 ? 'UNAUTHORIZED' : status === 422 ? 'VALIDATION_ERROR' : 'HTTP_ERROR'; return { ok: false, status, error: { code, message, details: body, isAuthError: status === 401, }, }; } return { ok: true, data: body as T, status }; } catch (e: any) { const isAbort = e?.name === 'AbortError'; if (isAbort) { return { ok: false, error: { code: 'TIMEOUT', message: 'A requisi e7 e3o demorou demais. Tente novamente.', isTimeout: true }, }; } return { ok: false, error: { code: 'NETWORK_ERROR', message: 'Sem conex e3o ou servidor indispon edvel.', details: String(e), isNetworkError: true, }, }; } finally { clearTimeout(timeoutId); }}Para cancelar manualmente (ex.: ao sair da tela), voc ea pode expor o AbortController ou aceitar um signal externo. Em apps reais, e9 comum o service receber um signal opcional.
3) Implementa e7 e3o com Axios (client com interceptors)
Com Axios, voc ea cria uma inst e2ncia e centraliza: baseURL, timeout, headers e interceptors para token, logs e normaliza e7 e3o de erros.
import axios, { AxiosError, AxiosInstance } from 'axios';const api: AxiosInstance = axios.create({ baseURL: 'https://api.seudominio.com', timeout: 15000, headers: { 'Content-Type': 'application/json' },});function normalizeAxiosError(err: unknown): ApiFailure { const e = err as AxiosError<any>; if (e.code === 'ECONNABORTED') { return { ok: false, error: { code: 'TIMEOUT', message: 'A requisi e7 e3o demorou demais. Tente novamente.', isTimeout: true } }; } if (!e.response) { return { ok: false, error: { code: 'NETWORK_ERROR', message: 'Sem conex e3o ou servidor indispon edvel.', details: e.message, isNetworkError: true } }; } const status = e.response.status; const data = e.response.data; const message = data?.message || data?.error || 'N e3o foi poss edvel completar a requisi e7 e3o.'; const code = status === 401 ? 'UNAUTHORIZED' : status === 422 ? 'VALIDATION_ERROR' : 'HTTP_ERROR'; return { ok: false, status, error: { code, message, details: data, isAuthError: status === 401 } };}export async function request<T>(fn: () => Promise<{ data: T; status: number }>): Promise<ApiResult<T>> { try { const res = await fn(); return { ok: true, data: res.data, status: res.status }; } catch (err) { return normalizeAxiosError(err); }}export { api };4) Interceptors: token, logs e correla e7 e3o de requisi e7 f5es
Interceptors permitem anexar o token automaticamente e registrar informa e7 f5es fateis para diagn f3stico (sem vazar dados sens edveis).
import { api } from './apiClient';import { authStore } from '../stores/authStore';api.interceptors.request.use((config) => { const token = authStore.getAccessToken(); if (token) config.headers.Authorization = `Bearer ${token}`; config.headers['X-Request-Id'] = `${Date.now()}-${Math.random().toString(16).slice(2)}`; if (__DEV__) { console.log('[HTTP] =>', config.method?.toUpperCase(), config.url); } return config;});api.interceptors.response.use( (response) => { if (__DEV__) { console.log('[HTTP] <=', response.status, response.config.url); } return response; }, (error) => { if (__DEV__) { const status = error?.response?.status; console.log('[HTTP] <= ERROR', status, error?.config?.url); } return Promise.reject(error); }Boas pr e1ticas de log: n e3o imprimir token, n e3o imprimir payloads sens edveis (senha, documentos), e preferir logs apenas em desenvolvimento.
Cancelamento de requisi e7 f5es (evitando setState ap f3s unmount)
Cancelamento e9 importante para: busca em tempo real, troca r e1pida de telas e evitar atualiza e7 f5es de estado quando o componente j e1 n e3o existe.
Com Axios + AbortController
import { api, request } from './apiClient';export async function searchUsers(q: string, signal?: AbortSignal) { return request<{ id: string; name: string }[]>(() => api.get('/users', { params: { q }, signal }) );}Uso em uma tela (exemplo simplificado):
import { useEffect, useState } from 'react';import { searchUsers } from '../services/userService';export function useUserSearch(query: string) { const [loading, setLoading] = useState(false); const [data, setData] = useState<any[]>([]); const [error, setError] = useState<string | null>(null); useEffect(() => { if (!query) return; const controller = new AbortController(); setLoading(true); setError(null); searchUsers(query, controller.signal).then((res) => { if (res.ok) setData(res.data); else setError(res.error.message); }).finally(() => setLoading(false)); return () => controller.abort(); }, [query]); return { loading, data, error };}Retries com backoff (quando e como aplicar)
Retry e9 fatil para falhas transit f3rias (rede inst e1vel, 502/503/504). N e3o e9 recomendado repetir automaticamente em erros de valida e7 e3o (422) ou autentica e7 e3o (401) sem uma estrat e9gia clara.
Implementando retry gen e9rico para ApiResult
function sleep(ms: number) { return new Promise((r) => setTimeout(r, ms));}type RetryOptions = { retries: number; baseDelayMs?: number; shouldRetry?: (result: ApiFailure) => boolean;};export async function withRetry<T>( task: () => Promise<ApiResult<T>>, opts: RetryOptions): Promise<ApiResult<T>> { const { retries, baseDelayMs = 400, shouldRetry } = opts; for (let attempt = 0; attempt <= retries; attempt++) { const res = await task(); if (res.ok) return res; const canRetry = shouldRetry ? shouldRetry(res) : !!(res.error.isNetworkError || res.error.isTimeout || (res.status && [502, 503, 504].includes(res.status))); if (!canRetry || attempt === retries) return res; const delay = baseDelayMs * Math.pow(2, attempt); await sleep(delay); } return { ok: false, error: { code: 'UNKNOWN', message: 'Falha inesperada.' } };}Exemplo de uso:
import { api, request } from './apiClient';import { withRetry } from './retry';export function getMe() { return withRetry( () => request(() => api.get('/me')), { retries: 2 } );}Autentica e7 e3o com token: login, refresh e armazenamento seguro
Modelo comum de autentica e7 e3o
- Access token: curto (minutos). Vai em
Authorization: Bearer .... - Refresh token: mais longo (dias). Usado para obter novo access token quando expirar.
Nem toda API usa refresh token. Quando n e3o houver, a estrat e9gia e9 redirecionar para login ao receber 401.
Armazenamento seguro do token
Evite guardar token em armazenamento n e3o seguro. Em React Native, use um cofre seguro (Keychain/Keystore) via biblioteca apropriada. Abaixo, um exemplo com uma API gen e9rica de “secure storage” (substitua pela biblioteca escolhida no projeto).
// secureStorage.ts (interface simples para isolar a depend eancia)export const secureStorage = { async set(key: string, value: string) { // implementar com Keychain/Keystore via biblioteca // ex.: setGenericPassword(key, value) ou equivalente }, async get(key: string) { // retornar string | null }, async remove(key: string) { // remover do cofre },};AuthStore: fonte fanica do token em mem f3ria
Mantenha o token em mem f3ria para uso r e1pido e sincronize com o armazenamento seguro para persist eancia.
type AuthState = { accessToken: string | null; refreshToken: string | null;};class AuthStore { private state: AuthState = { accessToken: null, refreshToken: null }; getAccessToken() { return this.state.accessToken; } getRefreshToken() { return this.state.refreshToken; } setTokens(tokens: { accessToken: string; refreshToken?: string | null }) { this.state.accessToken = tokens.accessToken; if (typeof tokens.refreshToken !== 'undefined') { this.state.refreshToken = tokens.refreshToken; } } clear() { this.state = { accessToken: null, refreshToken: null }; }}export const authStore = new AuthStore();Servi e7o de autentica e7 e3o: login e refresh
import { api, request } from '../http/apiClient';import { secureStorage } from '../storage/secureStorage';import { authStore } from '../stores/authStore';type LoginResponse = { accessToken: string; refreshToken?: string };export async function login(email: string, password: string) { const result = await request<LoginResponse>(() => api.post('/auth/login', { email, password })); if (result.ok) { authStore.setTokens({ accessToken: result.data.accessToken, refreshToken: result.data.refreshToken ?? null }); await secureStorage.set('accessToken', result.data.accessToken); if (result.data.refreshToken) await secureStorage.set('refreshToken', result.data.refreshToken); } return result;}export async function refresh() { const refreshToken = authStore.getRefreshToken() || (await secureStorage.get('refreshToken')); if (!refreshToken) { return { ok: false, error: { code: 'UNAUTHORIZED', message: 'Sess e3o expirada. Fa e7a login novamente.', isAuthError: true } } as const; } const result = await request<LoginResponse>(() => api.post('/auth/refresh', { refreshToken })); if (result.ok) { authStore.setTokens({ accessToken: result.data.accessToken, refreshToken: result.data.refreshToken ?? refreshToken }); await secureStorage.set('accessToken', result.data.accessToken); if (result.data.refreshToken) await secureStorage.set('refreshToken', result.data.refreshToken); } return result;}export async function logout() { authStore.clear(); await secureStorage.remove('accessToken'); await secureStorage.remove('refreshToken');}Refresh autom e1tico com interceptor (tratando 401 de forma robusta)
Quando a API retornar 401 por token expirado, voc ea pode tentar refresh e repetir a requisi e7 e3o original. O cuidado principal e9 evitar “tempestade” de refresh: v e1rias requisi e7 f5es falham ao mesmo tempo e todas tentam refresh. A solu e7 e3o e9 enfileirar enquanto um refresh est e1 em andamento.
import { api } from './apiClient';import { refresh, logout } from '../services/authService';import { authStore } from '../stores/authStore';let isRefreshing = false;let pending: Array<(token: string | null) => void> = [];function subscribe(cb: (token: string | null) => void) { pending.push(cb);}function notify(token: string | null) { pending.forEach((cb) => cb(token)); pending = [];}api.interceptors.response.use( (res) => res, async (error) => { const original = error.config; const status = error?.response?.status; if (status !== 401 || original?._retry) { return Promise.reject(error); } original._retry = true; if (isRefreshing) { return new Promise((resolve, reject) => { subscribe((token) => { if (!token) return reject(error); original.headers.Authorization = `Bearer ${token}`; resolve(api(original)); }); }); } isRefreshing = true; const ref = await refresh(); isRefreshing = false; if (!ref.ok) { notify(null); await logout(); return Promise.reject(error); } const newToken = authStore.getAccessToken(); notify(newToken); original.headers.Authorization = `Bearer ${newToken}`; return api(original); }Observa e7 e3o: o campo _retry e9 um marcador para evitar loop infinito. Em TypeScript, voc ea pode estender o tipo de config para permitir esse campo.
Prote e7 e3o de rotas (acesso autenticado vs p fablico)
A prote e7 e3o de rotas consiste em renderizar pilhas/abas diferentes dependendo do estado de autentica e7 e3o. A regra e9 simples: se existe token v e1lido, mostra e1rea logada; caso contr e1rio, mostra login/cadastro.
Passo a passo: bootstrap do token ao abrir o app
- Ao iniciar, carregue tokens do armazenamento seguro.
- Coloque em mem f3ria (authStore/estado global).
- Se houver refresh token e o access token estiver ausente/expirado, tente refresh.
import { useEffect, useState } from 'react';import { secureStorage } from '../storage/secureStorage';import { authStore } from '../stores/authStore';import { refresh } from '../services/authService';export function useAuthBootstrap() { const [ready, setReady] = useState(false); const [isSignedIn, setIsSignedIn] = useState(false); useEffect(() => { (async () => { const accessToken = await secureStorage.get('accessToken'); const refreshToken = await secureStorage.get('refreshToken'); if (accessToken) authStore.setTokens({ accessToken, refreshToken: refreshToken ?? null }); if (!accessToken && refreshToken) { const ref = await refresh(); if (ref.ok) { setIsSignedIn(true); setReady(true); return; } } setIsSignedIn(!!authStore.getAccessToken()); setReady(true); })(); }, []); return { ready, isSignedIn };}Na e1rvore de navega e7 e3o, voc ea decide qual stack renderizar com base em ready e isSignedIn. Enquanto ready for falso, mostre uma tela de splash/carregamento.
Estados de carregamento e mensagens amig e1veis
Uma experi eancia consistente depende de estados previs edveis:
- loading: exibir indicador e desabilitar bot f5es para evitar requisi e7 f5es duplicadas.
- success: renderizar dados.
- empty: renderizar estado vazio quando a lista vier vazia.
- error: mostrar mensagem amig e1vel e a e7 e3o de tentar novamente.
Mapeando erros t e9cnicos para mensagens de UI
import type { ApiFailure } from '../http/types';export function toUserMessage(failure: ApiFailure): string { switch (failure.error.code) { case 'NETWORK_ERROR': return 'Sem conex e3o. Verifique sua internet e tente novamente.'; case 'TIMEOUT': return 'Demorou mais do que o esperado. Tente novamente.'; case 'UNAUTHORIZED': return 'Sua sess e3o expirou. Entre novamente.'; case 'VALIDATION_ERROR': return 'Revise os dados informados.'; default: return failure.error.message || 'Ocorreu um erro. Tente novamente.'; }}Evite requisi e7 f5es duplicadas
Em a e7 f5es de bot e3o (ex.: login), desabilite enquanto loading for verdadeiro. Em listas com pull-to-refresh, ignore refresh se j e1 estiver carregando.
Logs e diagn f3stico: o que registrar e como
Logs ajudam a entender falhas em produ e7 e3o, mas precisam ser seguros. Registre:
- M e9todo, URL (sem query sens edvel), status.
- Um
X-Request-Idpara correlacionar com logs do backend. - Tempo de resposta (lat eancia).
- C f3digo de erro padronizado (
NETWORK_ERROR,HTTP_ERRORetc.).
N e3o registre: token, senha, refresh token, dados pessoais sens edveis.
Medindo lat eancia com interceptor
api.interceptors.request.use((config) => { (config as any).metadata = { start: Date.now() }; return config;});api.interceptors.response.use( (res) => { const start = (res.config as any).metadata?.start; const ms = start ? Date.now() - start : undefined; if (__DEV__) console.log('[HTTP] time(ms)=', ms, res.config.url); return res; }, (err) => { const start = (err.config as any)?.metadata?.start; const ms = start ? Date.now() - start : undefined; if (__DEV__) console.log('[HTTP] time(ms)=', ms, err.config?.url); return Promise.reject(err); }Organizando a camada de servi e7os (padr e3o recomendado)
Uma organiza e7 e3o simples e escal e1vel:
src/http/: client (Axios), normaliza e7 e3o de erros, retry, tipos.src/services/: fun e7 f5es por dom ednio (auth, users, orders).src/storage/: secure storage (isolando depend eancias).src/stores/: estado de autentica e7 e3o em mem f3ria.
Exemplo de service de dom ednio
import { api, request } from '../http/apiClient';type Me = { id: string; name: string; email: string };export function getMyProfile() { return request<Me>(() => api.get('/me'));}type UpdateMeInput = { name: string };export function updateMyProfile(input: UpdateMeInput) { return request<Me>(() => api.put('/me', input));}Checklist de robustez para consumo de APIs
| Item | Objetivo | Como implementar |
|---|---|---|
| Timeout | Evitar travas e esperas infinitas | timeout no Axios ou AbortController no fetch |
| Cancelamento | Evitar corrida de respostas e setState ap f3s unmount | AbortController e cleanup no useEffect |
| Padroniza e7 e3o de erros | UI consistente e menos ifs | ApiResult + normalizeAxiosError |
| Interceptors | Token, logs e refresh centralizados | api.interceptors |
| Refresh com fila | Evitar m faltiplos refresh simult e2neos | isRefreshing + subscribers |
| Retry com backoff | Resili eancia a falhas transit f3rias | withRetry com regras |
| Mensagens amig e1veis | Melhor UX em falhas | toUserMessage por c f3digo |
| Logs seguros | Diagn f3stico sem vazamento | Request-id, status, lat eancia; sem tokens |