Vamos otimizar o useOuvirCorridas.js do início ao fim, explicando o que acontece em cada etapa,
quais problemas podem surgir, como corrigi-los e como validar cada mudança antes de passar para a próxima.
A ideia é preservar as funcionalidades existentes: receber ofertas de corridas e entregas, filtrar por localização e categoria, controlar vagas, exibir o modal, tratar recusas e timeouts e acompanhar as mudanças em tempo real pelo Firebase.
O hook atual executa várias tarefas que dependem umas das outras. A ordem ideal para revisar o sistema é esta:
No arquivo, a inicialização está distribuída por vários useEffect: recuperação do histórico,
restauração do momento de login, atualização da posição, monitoramento do status e configuração das categorias.
O risco é começar a ouvir ofertas antes de todas as informações necessárias estarem prontas.
Antes de instalar listeners, o sistema precisa saber:
Nem todas essas condições precisam impedir a instalação de todo listener. Por exemplo, o monitoramento do status do motorista pode começar antes do GPS. Entretanto, o processamento de uma oferta que depende da localização deve aguardar uma posição válida.
Uma regra de inicialização pode ser representada assim:
const podeMonitorarOfertas =
Boolean(motoristaId) &&
isOnline === true &&
historicoCarregado === true &&
Boolean(momentoLogin) &&
categoriasEfetivasRef.current.length > 0;
ref, a implementação final deve garantir que uma alteração das categorias também atualize o estado reativo que controla o efeito.
Por que isso melhora o desempenho? Evita processar ofertas que serão descartadas por falta de dados e reduz trabalho desnecessário durante a inicialização.
O código atual atualiza posicaoRef sempre que minhaPosicao muda, mas a validação das
coordenadas é feita em outro ponto. Centralize a validação:
function posicaoValida(posicao) {
const latitude = Number(posicao?.latitude);
const longitude = Number(posicao?.longitude);
return (
Number.isFinite(latitude) &&
Number.isFinite(longitude) &&
latitude >= -90 &&
latitude <= 90 &&
longitude >= -180 &&
longitude <= 180
);
}
E, no processamento:
const posicao = posicaoRef.current;
if (!posicaoValida(posicao)) {
pendentesSemPosicaoRef.current.set(k, {
tipo,
oferta,
id,
});
return;
}
O hook persiste MOMENTO_LOGIN_KEY e usa esse valor em uma consulta startAt(momentoLogin).
Há um detalhe: o momento de login salvo no dispositivo não deve ser tratado como uma prova de que o motorista está autenticado, nem como garantia de que todas as ofertas relevantes serão encontradas.
Precisamos distinguir:
Também é necessário decidir se ofertas criadas antes de o motorista ficar online devem ser ignoradas ou se ele deve receber ofertas ainda válidas. O comportamento atual tende a restringir a busca pelo momento salvo.
O código possui duas consultas principais, uma para corridas e outra para entregas, cada uma com
onChildAdded e onChildChanged. Também cria listeners adicionais para ofertas
aguardando vaga e para a oferta aberta no modal.
O fluxo recomendado é:
onValue: o acompanhamento em tempo real do status de uma oferta pode ser importante para impedir que um modal permaneça aberto depois que outro motorista aceita a corrida.
A consulta atual utiliza orderByChild('criadoEmMs'). Configure o índice correspondente no
Realtime Database e verifique se o campo existe em todas as ofertas consultadas.
O limite de 30 registros é um começo razoável, mas não garante que todas as ofertas relevantes serão consideradas. Se houver mais de 30 ofertas recentes, o resultado dependerá da ordenação e dos filtros posteriores.
O ajuste correto depende de métricas como:
Meta: reduzir tráfego e processamento sem perder ofertas válidas.
A deduplicação atual utiliza uma janela de 30 segundos. Isso ajuda a reduzir eventos repetidos, mas não impede que duas chamadas já iniciadas executem operações assíncronas simultaneamente.
Uma solução é manter uma coleção de ofertas em processamento:
const processandoRef = useRef(new Set());
No início da função que processa a oferta:
const k = `${tipo}:${id}`;
if (processandoRef.current.has(k)) {
return;
}
processandoRef.current.add(k);
try {
// Validar a oferta e executar o processamento.
} finally {
processandoRef.current.delete(k);
}
processarOferta() com cuidado. A chave não deve ser liberada antes de terminar toda a operação crítica, e ofertas que precisem ser reprocessadas após uma alteração devem ter uma política explícita de reentrada.
A deduplicação por tempo e o bloqueio de processamento resolvem problemas diferentes: a primeira limita repetições; o segundo evita operações simultâneas na mesma oferta.
No código atual, uma oferta passa por verificações de status, ocupação, localização, idade, raio, categoria, histórico de notificações e reserva de vaga.
A sequência é importante: as validações mais baratas devem acontecer antes de operações que acessam a rede.
corrida ou entrega e que o ID é válido.Isso evita executar uma transação no Firebase para uma oferta que já deveria ter sido descartada.
A função atual retorna idade zero quando não encontra uma data válida, pois utiliza o horário atual como referência:
const base = criadoEmMs || agora;
Isso pode fazer uma oferta sem data ser tratada como recém-criada.
Se o campo de criação for obrigatório, valide-o antes:
function criadoEmValido(valor) {
return (
Number.isFinite(valor) &&
valor > 0 &&
valor <= Date.now() + 60000
);
}
A fórmula de Haversine utilizada no arquivo é adequada para estimar a distância geográfica entre dois pontos.
Para volumes elevados, você pode primeiro aplicar um filtro aproximado por latitude e longitude e executar Haversine apenas para os candidatos próximos. Isso reduz cálculos trigonométricos.
No entanto, com apenas 30 ofertas por consulta, essa otimização provavelmente será menos importante do que reduzir leituras, listeners e operações concorrentes.
A função reservarVaga() é uma das partes mais sensíveis do sistema.
Atualmente, a reserva é registrada em corridas_meta ou entregas_meta, dentro do nó
ofertados, com um limite de três motoristas e uma validade de 35 segundos.
É importante separar três conceitos:
Oferta enviada
O motorista foi selecionado para visualizar a oportunidade.
Reserva temporária
O motorista tem uma vaga para responder durante um prazo.
Corrida aceita
A atribuição foi confirmada e não pode ser aceita por outro motorista.
A reserva temporária não deve, sozinha, significar que a corrida foi aceita.
O código considera uma reserva expirada quando agora - em > TTL_OFERTADO_MS. Isso é funcional
como mecanismo de limpeza, mas utiliza o relógio do dispositivo que executa a transação.
Para uma implementação mais confiável:
ofertados poderá causar contenção. Uma arquitetura baseada em servidor pode ser necessária para distribuir vagas em escala.
Esta é a mudança arquitetural mais importante.
Quando um motorista toca em "Aceitar", o sistema precisa verificar e alterar o estado da corrida como uma única operação lógica. Dois motoristas não podem aceitar a mesma corrida simultaneamente.
A operação de aceitação deve verificar no servidor:
O servidor deve então confirmar o motorista vencedor e atualizar o estado da corrida atomicamente. O hook deve tratar o resultado confirmado, não assumir que a aceitação ocorreu apenas porque uma gravação foi enviada.
O hook controla ofertaParaModal, uma fila ordenada por distância e um timeout de 10 segundos.
Além disso, acompanha mudanças no status da oferta para fechar o modal quando ela deixa de estar disponível.
O fluxo ideal é:
Na implementação atual, setOfertaParaModal(null) pode ocorrer quando a oferta deixa de estar
disponível, mas a lógica que seleciona a próxima oferta não é centralizada em todos os caminhos.
Isso pode deixar a fila parada até outro evento acontecer.
Uma melhoria é criar uma única função responsável por avançar a fila e chamá-la de forma consistente quando:
O hook possui funções para aceitar, recusar, liberar reservas, registrar eventos e permitir novas tentativas. É importante que todas essas operações sigam uma sequência coerente.
A sequência recomendada é:
Se a aceitação falhar, o aplicativo deve receber o motivo e atualizar a interface sem mostrar a corrida como confirmada.
A recusa precisa ser idempotente: executar a mesma operação duas vezes não deve causar efeitos duplicados.
A função deve verificar se a oferta ainda está ativa para aquele motorista, registrar a recusa uma única vez e liberar a reserva correspondente.
recusarOferta() usa setTimeout(() => abrirProximaOferta(), 300). Esse atraso pode funcionar visualmente, mas não garante que a fila esteja consistente quando o callback for executado.
É melhor coordenar a remoção da oferta, a liberação da reserva e a escolha da próxima oferta em um fluxo controlado, com tratamento explícito de erros.
O arquivo usa um intervalo de 90 segundos e um máximo de duas novas tentativas para recusas. Esses parâmetros devem permanecer configuráveis, mas é importante esclarecer o significado de cada tentativa.
Uma nova tentativa deve ocorrer somente quando:
A limpeza dos recursos é essencial para que o sistema permaneça estável depois de horas de uso.
No código atual, existem listeners, intervalos e timeouts em vários efeitos. Alguns já possuem funções de cancelamento, o que é positivo, mas precisamos garantir que nenhum caminho deixe recursos ativos indevidamente.
Ao ficar offline, trocar de motorista ou desmontar o hook, a aplicação deve:
await.
Quando a conexão é interrompida, o Firebase pode reconectar e entregar novos eventos. O aplicativo deve suportar:
Na reconexão, revalide o estado real da oferta no Firebase antes de apresentá-la. Não confie apenas na cópia que ficou na memória.
O teste não deve terminar depois de abrir e aceitar uma corrida. Execute sessões prolongadas e acompanhe o crescimento de memória, o número de listeners, as gravações e o tempo de resposta.
Uma boa meta é que, depois de aceitar ou recusar uma oferta, os recursos associados sejam liberados e a quantidade de listeners permaneça dentro do esperado.
Use esta lista de verificação durante o desenvolvimento.
Eu implementaria as mudanças nesta ordem: