Depuração de Frontend em Tempo de Execução: inspecione o estado ativo antes de reler o código-fonte
Os dados estão corretos na rede mas a interface está errada. Isto é um bug de runtime/timing, e o instinto que fará você perder tempo é reler os arquivos-fonte. Resista a ele. Reordene o reflexo.
Quando usar
Invoke durante a depuração, quando o sintoma aparecer — não no início da tarefa. Use quando TODAS estas condições se aplicarem:
- Um componente React renderiza conteúdo vazio, desatualizado ou nenhum conteúdo.
- Os dados que ele precisa são sabidamente corretos na origem (a API/backend retorna a resposta certa).
- A lacuna está em algum lugar entre "resposta correta na rede" e "renderização correta na tela".
Frases típicas: "a API retorna os dados certos mas o componente está em branco", "react-query está travado", "a requisição é 200 mas a UI nunca atualiza", "funciona após um refresh mas não no primeiro carregamento".
Para o que NÃO serve
- Erros de build / compilação — isso é um bug de toolchain, não de runtime.
- Problemas exclusivamente de estilo / layout — os dados renderizam, só estão com aparência errada.
- A API genuinamente retorna dados errados ou nenhum dado — isso é um bug normal de backend/contrato. Verifique a resposta na rede PRIMEIRO; se a resposta estiver realmente errada ou vazia, pare e corrija o backend/contrato. Esta skill é apenas para quando a rede está correta mas a UI não está.
A reordenação (a lição fundamental)
-
Inspecione o estado de runtime ativo ANTES de reler o código-fonte. O reflexo é reler o componente, schema, layout e a fiação do provedor procurando o bug. Quando os dados estão corretos na rede mas a UI não está, esse reflexo é uma armadilha: o bug frequentemente NÃO está em um único arquivo-fonte, mas em uma interação de runtime/timing entre peças que são, individualmente, corretas. A revisão de código-fonte é estruturalmente cega a essa classe de bugs. Olhe para o estado ativo primeiro.
-
Um 200 no painel de rede pode enganar ativamente. Uma requisição mostrando 200 significa que a resposta chegou. Isso NÃO significa que os dados alcançaram o componente ou foram commitados no cache. A resposta pode chegar e ser descartada, removida ou nunca commitada. Não conclua "a busca funciona" a partir de um 200 — confirme que os dados realmente alcançaram o estado do componente consumidor.
-
Para react-query, inspecione o observador de consulta ativo do componente afetado, não apenas a rede. Leia os campos
status,fetchStatus,dataUpdateCounteerrordo observador.- Uma consulta travada em
status: "pending"/fetchStatus: "fetching"comdataUpdateCount: 0significa que o resultado da promise da queryFn nunca foi commitado no observador. Isso aponta para a consulta ter sido removida ou invalidada durante o voo — NÃO para uma falha na busca. - Confirme que a própria queryFn está saudável forçando o observador a refazer a busca. Se uma refetch manual resolver corretamente e imediatamente, a busca/cliente/schema estão todos bem, e o verdadeiro bug é que a consulta inicial foi abandonada (removida por uma purga de cache, invalidada por um efeito, ou derrubada por um remount). Esta única observação distingue "o caminho de dados está quebrado" de "o caminho de dados está bem mas a consulta foi morta" — bugs completamente diferentes.
- Uma consulta travada em
-
Uma vez localizado como "consulta abandonada durante o voo", procure upstream no gerenciamento de ciclo de vida/cache. A causa é algo que foi executado no mesmo momento em que a consulta começou: um
removeQueries/invalidateQueries/ reset de cache, ou uma corrida de mount/unmount/remount. Olhe para efeitos de transição de autenticação/sessão, purgas de troca de principal/tenant e remounts de provedor — NÃO para o código de busca.
Caminho de decisão
UI errada, dados corretos na origem?
→ confirme que a resposta na rede está realmente correta (não é trabalho desta skill se não estiver)
→ leia o observador de consulta ativo do componente afetado
status pending + fetchStatus fetching + dataUpdateCount 0?
→ force uma refetch naquele observador
refetch resolve instantaneamente e corretamente?
→ queryFn está OK. Bug = consulta inicial abandonada.
Caça upstream: removeQueries/invalidateQueries/reset,
corrida de remount/unmount, efeito de purga de auth/sessão/tenant.
refetch também falha?
→ caminho de dados genuinamente quebrado. Inspecione queryFn/cliente/schema.
tem dados mas UI ainda em branco?
→ bug de render/seletor/condicional no componente, agora releia o código-fonte.
Ferramentas de inspeção ao vivo (pistas, não APIs estáveis)
Estes snippets alcançam o observador react-query de um componente a partir de uma página em execução, caminhando pela árvore de fibras do React a partir de um elemento DOM renderizado, leem o estado atual da consulta do observador e forçam uma refetch para provar que a queryFn funciona.
Estes snippets são sensíveis à versão. Eles foram derivados contra @tanstack/react-query v5.90 e internals de fibras do React 18, dependem de internals não documentados e IRÃO se desatualizar. Trate-os como pontos de partida para adaptar — reverifique os nomes dos campos e a forma das fibras contra as versões instaladas do projeto antes de confiar em qualquer resultado. Eles são pistas, não APIs garantidamente estáveis.
Caminhe pela árvore de fibras a partir de um nó DOM para encontrar hooks/observadores:
// Escolha um elemento DOM renderizado pelo componente afetado (ex: via $0 nas devtools).
const el = $0; // ou document.querySelector('[data-testid="..."]')
const key = Object.keys(el).find(k => k.startsWith('__reactFiber$'));
let fiber = el[key];
// Caminhe para cima/baixo na árvore de fibras; memoizedState é uma lista ligada de hooks.
// react-query armazena seu observador no memoizedState de um hook.
function* fibers(root) {
const stack = [root];
while (stack.length) {
const f = stack.pop();
if (!f) continue;
yield f;
if (f.child) stack.push(f.child);
if (f.sibling) stack.push(f.sibling);
}
}
// Escaneie o estado do hook por algo que pareça um QueryObserver.
function findObservers(fiber) {
const found = [];
for (const f of fibers(fiber)) {
let hook = f.memoizedState;
while (hook) {
const s = hook.memoizedState;
if (s && typeof s === 'object') {
// Instâncias de QueryObserver expõem getCurrentResult()/getCurrentQuery().
if (typeof s.getCurrentResult === 'function') found.push(s);
// Algumas versões aninham; verifique também wrappers comuns.
if (s.observer && typeof s.observer.getCurrentResult === 'function') found.push(s.observer);
}
hook = hook.next;
}
}
return found;
}
const observers = findObservers(fiber);
Leia o estado da consulta ativa de um observador:
const obs = observers[0];
const r = obs.getCurrentResult();
console.log({
status: r.status, // 'pending' | 'error' | 'success'
fetchStatus: r.fetchStatus, // 'fetching' | 'paused' | 'idle'
dataUpdateCount: obs.getCurrentQuery().state.dataUpdateCount,
error: r.error,
});
// pending + fetching + dataUpdateCount 0 ==> abandonada durante o voo, não uma falha na busca.
Force uma refetch para provar que a queryFn está saudável:
obs.refetch().then(r => console.log('refetch:', r.status, r.data)).catch(e => console.log('refetch falhou:', e));
// Resolve instantaneamente e corretamente ==> queryFn/cliente/schema estão bons;
// a consulta original foi morta upstream.
Se o projeto expuser o QueryClient (ex: no window para depuração, ou acessível a partir de uma fibra de provedor), prefira queryClient.getQueryCache().findAll() e inspecione o state e observers de cada consulta diretamente — é mais robusto do que caminhar por fibras, quando disponível.
Conclusão
Quando você tiver localizado o bug:
- Declare em qual lado da bifurcação ele caiu: "caminho de dados quebrado" vs "consulta abandonada".
- Se abandonada, nomeie a ação de ciclo de vida/cache upstream que a matou.
- Só então releia o arquivo-fonte específico responsável — agora você sabe qual é.