Modernização React: fluxos assíncronos que se leem de cima para baixo

Participei da migração de um frontend legado em JavaScript e jQuery para React. Além de componentes reutilizáveis, a migração é uma chance de decidir como fluxos assíncronos serão lidos por quem mantém o código.

Participação
Em equipe Participação em equipe
Contexto
DPMS, via Oxeanbits

Contexto

No DPMS, parte da interface dependia de JavaScript e jQuery legados. A equipe migrou essas telas para React, com objetivos de manutenibilidade, reutilização de componentes, extensibilidade e um modelo de renderização mais claro.

A migração foi um trabalho de equipe. Participei dela e da discussão sobre como estruturar os componentes e os fluxos.

Problema

Trocar jQuery por React não garante, sozinho, código mais fácil de manter. Um ponto específico costuma pesar: ações do usuário que disparam várias chamadas de API dependentes.

Cada etapa pode falhar e precisa de feedback, e a operação final só deve acontecer quando todas as etapas necessárias tiverem sucesso. Se esse processo for dividido em vários useEffect que reagem a mudanças de estado, a sequência deixa de estar escrita em algum lugar: ela precisa ser deduzida.

Minha responsabilidade

Participei da migração e ajudei a definir a arquitetura React que substituiu as interfaces legadas, com atenção à reutilização de componentes, à separação de responsabilidades e à legibilidade dos fluxos assíncronos.

Não fui o único autor da migração; este caso registra uma decisão de engenharia que defendo e aplico.

Decisão

Quando uma ação do usuário dispara um processo, prefiro tornar a orquestração explícita: uma função com nome que revela a intenção, etapas em sequência legível e tratamento de erro no ponto em que a falha acontece.

Dependendo do problema, um handler de evento, uma função assíncrona, um custom hook ou uma abstração de data fetching pode ser a melhor forma. Efeitos continuam úteis para sincronizar com sistemas externos, como assinaturas e conexões, com cleanup ou cancelamento quando apropriado.

Alternativas e trade-offs

Análise atual, escrita para este site. Não é um registro das alternativas avaliadas na época.

  • Efeitos encadeados podem funcionar e às vezes parecem mais "reativos". O custo aparece na leitura: as dependências ficam implícitas em arrays de dependência e flags de estado, e o efeito não tem um nome que diga qual processo de negócio executa.
  • Uma função explícita concentra o fluxo, mas pede cuidado com cancelamento quando o componente sai da tela e com estados de carregamento. Custom hooks ajudam a nomear essas responsabilidades sem voltar a espalhá-las.
  • O ponto é julgamento e legibilidade, não uma regra contra useEffect.

Implementação

Os critérios que levo para revisões e decisões de arquitetura em React:

  1. Nomes que revelam intenção

    O processo de negócio aparece como uma função, por exemplo approveReport, em vez de surgir da combinação de efeitos.

  2. Sequência visível

    Chamadas dependentes ficam em ordem no código; quem lê não precisa reconstruir a cadeia a partir de mudanças de estado.

  3. Falhas tratadas onde acontecem

    Cada etapa pode falhar com uma mensagem útil, e a operação final só roda quando as anteriores tiveram sucesso.

  4. Efeitos para sincronização

    useEffect fica reservado para sincronizar com sistemas externos, com cleanup quando necessário.

Visualização

Efeitos encadeados

const [report, setReport] = useState(null);const [metrics, setMetrics] = useState(null);const [pdf, setPdf] = useState(null);const handleApprove = () =>  saveReport(draft)    .then(setReport)    .catch(showError);useEffect(() => {  if (!report) return;  recalculateMetrics(report.id)    .then(setMetrics)    .catch(showError);}, [report]);useEffect(() => {  if (!metrics) return;  generatePdf(metrics.reportId)    .then(setPdf)    .catch(showError);}, [metrics]);useEffect(() => {  if (!pdf) return;  publish(pdf.reportId).catch(showError);}, [pdf]);

Pontos de leitura para entender a sequência: 3 efeitos, 3 estados intermediários e os arrays de dependência

Função nomeada

async function approveReport(draft: Draft) {  const report = await saveReport(draft);  const metrics = await recalculateMetrics(report.id);  const pdf = await generatePdf(metrics.reportId);  await publish(pdf.reportId);}const handleApprove = async () => {  try {    await approveReport(draft);    showSuccess();  } catch (error) {    showError(error);  }};

Pontos de leitura para entender a sequência: 1 função, lida de cima para baixo

  1. Salvar relatório Aguardando
  2. Recalcular indicadores Aguardando
  3. Gerar PDF Aguardando
  4. Publicar Aguardando

A interação fica disponível quando o JavaScript carrega. O código acima já descreve os dois fluxos.

Mesmo processo, duas leituras Código ilustrativo e chamadas simuladas. Ao aprovar um relatório: salvar, recalcular indicadores e gerar o PDF; publicar somente se tudo der certo. useEffect continua adequado para sincronizar com sistemas externos, como assinaturas e conexões. Custom hooks também podem dar nome a responsabilidades.

Resultado

A migração buscou componentes reutilizáveis e um modelo de renderização mais claro que o das telas em jQuery. O resultado que destaco aqui é qualitativo: um critério explícito para que fluxos assíncronos possam ser lidos, revisados e alterados sem caçar dependências pela tela.

A comparação interativa acima usa código ilustrativo e chamadas simuladas; ela não reproduz o código do projeto.

Tecnologias

  • React
  • JavaScript
  • jQuery

Conte qual problema você precisa resolver

Quer contratar para sua equipe ou resolver um problema do seu produto? Conte o contexto e o resultado esperado. Podemos conversar sobre uma posição de Senior Full Stack Developer ou Tech Lead, desenvolvimento de projeto ou consultoria técnica.

WhatsApp

+55 71 99705-3401

Abre o WhatsApp com uma mensagem inicial que você pode editar. Nada é enviado automaticamente.

Também estou no GitHub e no LinkedIn.