| name | console-debugging |
| description | Console.log debugging patterns for React/TypeScript. Use when tracking down bugs involving data transformations, conditional logic, state management, or when you need to trace data flow through pipelines. |
Console.log Debugging Skill
Effective console.log debugging patterns for React/TypeScript applications. Use when tracking down bugs involving data transformations, conditional logic, or state management.
Core Principle: Funnel from Broad to Targeted
Log everything → Too noisy, can't see signal
Filter to test case → Now logs are relevant
Count in vs out → Quantify the bug
Trace through branches → See exactly which logic is wrong
Root cause → Fix with confidence
Pattern 1: Test Case Filter
Problem: Logging every occurrence floods the console with 50+ irrelevant entries.
Solution: Filter all logs to ONE specific test case you can reproduce.
console.log("[GraphEdge] rendered:", item.id);
const isDebugCase = item.id.startsWith("pwny");
if (isDebugCase) {
console.log("[GraphEdge] rendered:", item.id);
}
const debugIds = new Set(["pwny", "komv", "ymzk"]);
const isDebug = debugIds.has(item.id.slice(0, 4));
Pattern 2: Count Expected vs Actual
Problem: You know something's wrong but not where data is being lost.
Solution: Log counts at pipeline boundaries.
console.log("[transform] input count:", items.length);
console.log("[transform] output count:", result.length);
let skipped = 0;
for (const item of items) {
if (shouldSkip(item)) {
skipped++;
continue;
}
}
console.log("[transform] skipped:", skipped, "processed:", result.length);
Pattern 3: Branch Tracing with Labels
Problem: Items are being filtered/transformed wrong, but you can't see which code path they take.
Solution: Log at EVERY branch exit with a descriptive label.
for (const item of items) {
const isDebug = debugIds.has(item.id);
if (conditionA) {
if (isDebug) console.log("[item] PATH A (conditionA true):", item.id);
} else if (conditionB) {
if (isDebug) console.log("[item] PATH B (conditionB true):", item.id);
} else if (shouldSkip) {
if (isDebug) console.log("[item] SKIPPED (shouldSkip):", item.id);
continue;
} else {
if (isDebug) console.log("[item] PATH DEFAULT:", item.id);
}
if (isDebug) console.log("[item] ADDED:", item.id);
result.push(item);
}
Output reveals the bug:
[item] ADDED: "2c778760"
[item] PATH A (conditionA true): "88dafdf7" ← Why is this going to A??
[item] SKIPPED (shouldSkip): "4f6c4330"
Pattern 4: Log the Deciding Factors
Problem: You see which branch fired but not WHY.
Solution: Log the values that determined the branch.
if (isDebug) console.log("[item] SKIPPED");
if (isDebug) console.log("[item] SKIPPED:", {
reason: "source is hidden",
sourceId: item.sourceId,
isInHiddenSet: hiddenSet.has(item.sourceId),
isInExpandedStack: expandedStack.has(item.sourceId),
});
Pattern 5: Verify State at Multiple Points
For React state bugs, log state at key lifecycle points:
function toggleExpanded(id: string) {
console.log("[toggleExpanded] called with:", id);
setExpanded(prev => {
const next = new Set(prev);
next.add(id);
console.log("[toggleExpanded] new state:", [...next]);
return next;
});
}
const result = useMemo(() => {
console.log("[useMemo] expandedSet:", [...expanded]);
console.log("[useMemo] result count:", result.length);
return result;
}, [expanded, otherDeps]);
Log Format Standard
console.log(`[${componentOrFunction}] ${ACTION}: ${identifier}`, { details });
- Prefix:
[ComponentName] or [functionName] in brackets
- Action: UPPERCASE verb - ADDED, SKIPPED, FILTERED, TRANSFORMED, RENDERED
- Identifier: The item ID for filtering/correlation
- Details object: Additional context (use object so console shows expandable)
Examples:
console.log("[EdgeFilter] SKIPPED: edge-123", { reason: "hidden", sourceId: "abc" });
console.log("[useMemo] RECOMPUTED:", { inputCount: 5, outputCount: 1, deps: [...deps] });
console.log("[GraphEdge] RENDERED: pwny", { isCollapsed: true, stackId: "xyz" });
Debugging Priority/Ordering Bugs
These bugs occur when multiple conditions COULD match but fire in wrong order.
Symptom: An item matches condition A but SHOULD have matched condition B first.
Detection pattern:
const matchesA = conditionA(item);
const matchesB = conditionB(item);
if (isDebug && matchesA && matchesB) {
console.log("[item] MATCHES BOTH A and B:", item.id, {
willTakePath: "A",
shouldTakePath: "B?"
});
}
if (matchesA) {
}
Fix pattern: Check the higher-priority condition FIRST:
if (matchesA) { ... }
else if (matchesB) { ... }
if (matchesB) { ... }
else if (matchesA) { ... }
Anti-Patterns to Avoid
1. Truncating IDs in Logs
console.log("id:", item.id.slice(0, 8));
console.log("id:", item.id);
2. Logging in Render Instead of Data Pipeline
For "wrong data displayed" bugs, log the data transformation, not the display:
return <div>{items.map(i => { console.log(i); return <Item {...i} /> })}</div>
const processedItems = useMemo(() => {
console.log("[processItems] input:", items.length);
const result = items.filter(...).map(...);
console.log("[processItems] output:", result.length);
return result;
}, [items]);
3. Refactoring Before Confirming Hypothesis
console.log("[hover] state:", hoverState);
console.log("[hover] expected:", expectedState);
Quick Reference Checklist
When debugging with console.log: