skills/react-ts-debugging/SKILL.md
Debug React and TypeScript bugs through a collaborative, log-driven loop with the developer instead of guessing from source code. Use this whenever a user is hunting a bug in a React/TS/JS app — "why is my component re-rendering", "this state won't update", "useEffect runs twice / infinitely", "my fetch returns undefined", "this works sometimes but not always", "the value is wrong but I don't know why", or any "help me figure out what's going on" request. Trigger it even when the user just pastes a broken component and asks what's wrong: rather than eyeballing the file, run a real debugging session where you place strategic console logs, the developer runs the app and collects the output, and that output drives the next step. Prefer this over read-and-guess debugging.
npx skillsauth add joyco-studio/skills react-ts-debuggingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You cannot see the running app. The developer can. So treat every bug as a joint investigation where your job is to place instruments (logs) that turn the invisible runtime into evidence, and the developer's job is to run the app, reproduce the bug, and bring the evidence back. The logs they collect ARE the data you reason over — not the source code.
Reading a file and announcing "ah, the bug is on line 42" is a guess. Sometimes the guess is right, but you have no way to know, and when it's wrong you've spent the user's trust and possibly broken working code. A log that fires (or fails to fire) with a specific value is a fact. Build the diagnosis out of facts.
This reframes the whole interaction: you are not a code reader who occasionally asks questions. You are running an experiment, and the developer is your hands and eyes in the lab.
Run this cycle. Do not skip ahead to a fix.
The discipline that makes this work is the STOP after step 3. Resist the urge to add logs and immediately also rewrite the logic. If you change behavior before you understand it, you contaminate the experiment and you'll never know whether your "fix" fixed the real bug or just moved it.
Ask for, or confirm, three things:
If the developer can't reproduce it reliably yet, that's the first thing to solve — sometimes a log that records inputs is how you find the repro.
Before adding a single log, write down what you think might be happening and why. Good hypotheses are specific and competing:
Either (a) the effect never runs because its dependency array is wrong, or (b) it runs but
fetchUserreturns before state updates, or (c) it runs and sets state but a second render overwrites it. A log at effect entry + one before/after setState will tell us which.
Stating hypotheses keeps the log set small and purposeful. If you can't say what a log is testing, don't add it — log spam buries the signal.
So that both you and the developer can find and remove all instrumentation in
one pass, tag every inserted line with a consistent token. Use a // @debug
comment on the code line and a recognizable prefix in the log string:
console.log("[DBG] Cart.render", { itemCount: items.length }); // @debug
At cleanup time, grep -rn "@debug" src/ finds every line. Pick the token at
the start of the session and tell the developer what it is, so they trust that
nothing permanent is being smuggled into their codebase.
A lone console.log(value) in a 200-component app is useless. Always include:
"[DBG] useAuth effect" or "[DBG] Row.onClick id=42".structuredClone the slice you care about.console.count()
for render counts, or include performance.now().console.log({ a, b }) — values at a point. Wrap in an object so labels stick.console.count("[DBG] Foo render") — how many times something runs (re-renders,
effect fires). Perfect for "why does this run N times".console.table(arrayOfObjects) — comparing rows of data.console.trace("[DBG] who called setOpen") — the call stack that led here, for
"what is triggering this".console.group / console.groupEnd — bracket a render or a handler so related
logs read as one unit.console.warn / console.error — make a specific "this branch should never
run" marker stand out in a noisy console.Bugs almost always live at a boundary. Instrument the edges and let the values tell you which side is wrong:
setState (the value you're
setting) and in the next render (the value you actually got). React batches and
defers, so "I set it to X" and "it became X" are different events.await/fetch, right after it
resolves, and in the catch. The most common React bug shape is "state set
after the component moved on."if/ternary so
you can see which path executed.TypeScript types are erased at runtime. A value typed User can be undefined,
a number can arrive as a "42" string from an API, and an any or a !
assertion can hide a lie. When the bug smells like a type/shape mismatch, log the
actual runtime nature, which the types cannot guarantee:
console.log("[DBG] api payload", {
value: data,
type: typeof data,
isArray: Array.isArray(data),
ctor: data?.constructor?.name,
}); // @debug
See references/log-recipes.md for a catalog of ready-to-paste instrumentation
for the common React/TS bug shapes (re-render storms, stale closures, infinite
effects, StrictMode double-invocation, lost state, race conditions, narrowing
failures, === identity surprises). Read it once you have a hypothesis about
which shape the bug is, and adapt the matching recipe.
When the logs are placed, end your turn with a clear, short handoff:
@debug marker, so they know it's
removable).Then stop and let them run it. Do not pre-write the fix "to save a round trip." You don't yet know what you're fixing.
When the developer pastes logs, treat them as evidence:
If the evidence doesn't yet name the cause, go back to step 2 with a narrower question and a smaller, sharper log set. Each round should halve the search space, like a binary search through the execution.
grep -rn "@debug" src/ (or the chosen token) and
delete them all. Leaving instrumentation behind is a bug of its own — noisy
consoles, leaked data, confused teammates.[DBG] logs to production.The log loop is the default and the priority, but name the better tool when it clearly fits, and still drive it collaboratively:
debugger — when you need to inspect a rich object tree or
step line-by-line; the developer pastes back what they see at the breakpoint,
same loop.Whatever the instrument, the contract is the same: you propose the experiment, the developer runs it, the result drives the next step.
tools
Add sound effects, UI audio, and ambient sound to a web app using the @joycostudio/suno library. Use when the user wants to play audio on button clicks, hover states, game events, or ambient loops, and when they mention @joycostudio/suno, Suno, AudioSource, Voice, or Mixer.
tools
Analyze a Chrome DevTools Performance trace JSON file for performance anomalies, producing a structured audit report with critical issues, warnings, metrics, timeline hotspots, and actionable recommendations.
development
Analyze a bye-thrash layout thrashing report array. Parses stack traces, identifies user-code functions causing forced reflows, locates the offending style-write → layout-read pairs in source files, and produces a structured fix-suggestion report.
development
Author or refactor a skill in this repo. Use when the user asks to "create a skill", "write a skill", "add a new skill", "document this as a skill", or to restructure an existing SKILL.md (split it up, slim it down, fix the frontmatter). Covers frontmatter conventions, file layout, and the rule for splitting deep reference material into linked docs instead of bloating SKILL.md.