Frontend-Runtime-Debugging: Live-Status inspizieren, bevor Quellcode erneut gelesen wird
Die Daten auf der Leitung sind korrekt, aber die UI ist falsch. Dies ist ein Laufzeit-/Timing-Fehler, und der Instinkt, der Zeit verschwendet, ist das erneute Lesen von Quelldateien. Widerstehe ihm. Ordne den Reflex um.
Wann anwenden
Mid-Debugging aufrufen, wenn das Symptom auftritt – nicht zu Beginn einer Aufgabe. Anwenden, wenn ALLE folgenden Bedingungen erfüllt sind:
- Eine React-Komponente rendert nichts, veraltete oder leere Inhalte.
- Die benötigten Daten sind an der Quelle korrekt (die API/der Backend gibt die richtige Antwort zurück).
- Die Lücke liegt irgendwo zwischen „korrekte Antwort auf der Leitung“ und „korrektes Rendern auf dem Bildschirm“.
Typische Formulierungen: „die API gibt die richtigen Daten zurück, aber die Komponente ist leer“, „react-query hängt fest“, „die Anfrage ist 200, aber die UI aktualisiert sich nie“, „es funktioniert nach einem Refresh, aber nicht beim ersten Laden“.
Wofür dies NICHT geeignet ist
- Build-/Kompilierungsfehler – das ist ein Toolchain-Fehler, kein Laufzeitfehler.
- Reine Styling-/Layout-Probleme – die Daten werden gerendert, sie sehen nur falsch aus.
- Die API gibt tatsächlich falsche oder keine Daten zurück – das ist ein normaler Backend-/Vertragsfehler. Überprüfe zuerst die Response auf der Leitung; wenn die Antwort tatsächlich falsch oder leer ist, stoppe und behebe den Backend-/Vertragsfehler. Diese Skill ist nur für den Fall, dass die Leitung korrekt ist, aber die UI nicht.
Die Umordnung (die tragende Lektion)
-
Live-Runtime-Status INSPIZIEREN, BEVOR Quellcode erneut gelesen wird. Der Reflex ist, die Komponente, das Schema, das Layout und die Provider-Verdrahtung erneut zu lesen, um den Fehler zu jagen. Wenn die Daten auf der Leitung korrekt sind, aber die UI nicht, ist dieser Reflex eine Falle: Der Fehler liegt häufig NICHT in einer einzelnen Quelldatei, sondern in einer Laufzeit-/Timing-Interaktion zwischen Teilen, die jeweils einzeln korrekt sind. Die Code-Review ist strukturell blind für diese Klasse. Sieh dir zuerst den Live-Status an.
-
Ein 200 im Netzwerkpanel kann aktiv in die Irre führen. Eine Anfrage mit 200 bedeutet, dass die Antwort angekommen ist. Es bedeutet NICHT, dass die Daten die Komponente erreicht oder in den Cache übernommen wurden. Die Antwort kann ankommen und dann verworfen, entfernt oder nie übernommen werden. Ziehe nicht den Schluss „der Fetch funktioniert“ aus einem 200 – bestätige, dass die Daten tatsächlich den Zustand der verbrauchenden Komponente erreicht haben.
-
Bei React-Query inspiziere den Live-Query-Observer der betroffenen Komponente, nicht nur das Netzwerk. Lies die Werte
status,fetchStatus,dataUpdateCountunderrordes Observers.- Ein Query, das bei
status: "pending"/fetchStatus: "fetching"mitdataUpdateCount: 0festhängt, bedeutet, dass das Ergebnis des queryFn-Promises nie im Observer gelandet ist. Das deutet darauf hin, dass die Query während der Ausführung entfernt oder ungültig gemacht wurde – NICHT auf einen fehlgeschlagenen Fetch. - Bestätige, dass der queryFn selbst gesund ist, indem du einen manuellen Refetch auf dem Observer erzwingst. Wenn ein manueller Refetch sofort und korrekt auflöst, sind Fetch/Client/Schema alle in Ordnung, und der eigentliche Fehler ist, dass die ursprüngliche Query gestrandet wurde (entfernt durch eine Cache-Bereinigung, ungültig gemacht durch einen Effekt oder abgerissen durch ein Remount). Diese einzelne Beobachtung unterscheidet „der Datenpfad ist kaputt“ von „der Datenpfad ist in Ordnung, aber die Query wurde getötet“ – völlig unterschiedliche Fehler.
- Ein Query, das bei
-
Wenn der Fehler auf „gestrandete Query während des Flugs“ eingegrenzt wurde, suche upstream im Lifecycle-/Cache-Management. Die Ursache ist etwas, das im selben Moment wie der Query-Start ausgeführt wurde: ein
removeQueries/invalidateQueries/ Cache-Reset oder ein Mount/Unmount/Remount-Race. Sieh dir Auth-/Session-Transition-Effekte, Principal-/Tenant-Switch-Bereinigungen und Provider-Remounts an – NICHT den Fetch-Code.
Entscheidungspfad
UI falsch, Daten an der Quelle korrekt?
→ bestätige, dass die Netzwerkantwort tatsächlich korrekt ist (nicht Aufgabe dieser Skill, wenn nicht)
→ lese den Live-Query-Observer der betroffenen Komponente
status pending + fetchStatus fetching + dataUpdateCount 0?
→ erzwinge einen Refetch auf diesem Observer
refetch löst sofort & korrekt auf?
→ queryFn ist IN ORDNUNG. Fehler = initiale Query gestrandet.
Jage upstream: removeQueries/invalidateQueries/reset,
Remount/Unmount-Race, Auth-/Session-/Tenant-Purge-Effekt.
refetch schlägt auch fehl?
→ Datenpfad wirklich kaputt. Inspiziere queryFn/Client/Schema.
hat Daten, aber UI immer noch leer?
→ Render-/Selector-/Conditional-Fehler in der Komponente, jetzt Quellcode erneut lesen.
Live-Inspektionswerkzeuge (Anhaltspunkte, keine stabilen APIs)
Diese erreichen den React-Query-Observer einer Komponente auf einer laufenden Seite, indem sie den React-Fiber-Baum von einem gerenderten DOM-Element aus durchlaufen, den aktuellen Query-Zustand des Observers auslesen und einen Refetch erzwingen, um zu beweisen, dass der queryFn funktioniert.
Diese Code-Snippets sind versionsabhängig. Sie wurden gegen @tanstack/react-query v5.90 und React 18 Fiber-Interna abgeleitet, stützen sich auf undokumentierte Interna und werden DRIFTEN. Behandle sie als Ausgangspunkte zur Anpassung – überprüfe die Feldnamen und die Fiber-Struktur anhand der im Projekt installierten Versionen, bevor du einem Ergebnis vertraust. Sie sind Anhaltspunkte, keine garantiert stabilen APIs.
Durchlaufe den Fiber-Baum von einem DOM-Knoten aus, um Hooks/Observer zu finden:
// Wähle ein DOM-Element, das von der betroffenen Komponente gerendert wird (z.B. via $0 in den Devtools).
const el = $0; // oder document.querySelector('[data-testid="..."]')
const key = Object.keys(el).find(k => k.startsWith('__reactFiber$'));
let fiber = el[key];
// Durchlaufe den Fiber-Baum auf/abwärts; memoizedState ist eine verkettete Liste von Hooks.
// React-Query speichert seinen Observer auf dem memoizedState eines Hooks.
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);
}
}
// Durchsuche den Hook-Status nach etwas, das wie ein QueryObserver aussieht.
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') {
// QueryObserver-Instanzen exponieren getCurrentResult()/getCurrentQuery().
if (typeof s.getCurrentResult === 'function') found.push(s);
// Einige Versionen verschachteln es; überprüfe auch gängige Wrapper.
if (s.observer && typeof s.observer.getCurrentResult === 'function') found.push(s.observer);
}
hook = hook.next;
}
}
return found;
}
const observers = findObservers(fiber);
Lese den Live-Query-Zustand eines Observers:
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 ==> gestrandet während des Flugs, kein fehlgeschlagener Fetch.
Erzwinge einen Refetch, um zu beweisen, dass der queryFn gesund ist:
obs.refetch().then(r => console.log('refetch:', r.status, r.data)).catch(e => console.log('refetch failed:', e));
// Löst sofort & korrekt auf ==> queryFn/Client/Schema sind in Ordnung;
// die ursprüngliche Query wurde upstream getötet.
Wenn das Projekt den QueryClient exponiert (z.B. auf window zum Debuggen oder über eine Provider-Fiber erreichbar), bevorzuge queryClient.getQueryCache().findAll() und inspiziere den state und die observers jeder Query direkt – das ist robuster als Fiber-Durchlauf, wenn verfügbar.
Abschluss
Wenn du den Fehler lokalisiert hast:
- Gib an, auf welcher Seite der Gabelung er liegt: „Datenpfad kaputt“ vs. „Query gestrandet“.
- Wenn gestrandet, nenne die Upstream-Lifecycle-/Cache-Aktion, die die Query getötet hat.
- Erst dann lies die spezifische verantwortliche Quelldatei erneut – jetzt weißt du, welche.