Not just that they did. The changed value, where it came from, what it cost, and how sure we are.
Every render tool can count renders. Counting is not the hard part.
UserProfile rendered 47 times.
UserProfile rendered because `user` changed by reference. Its values are identical. Dashboard recreated the object.
npm install react-render-detective
// once, at your entry point import { init } from "react-render-detective"; if (process.env.NODE_ENV !== "production") init(); // then wrap whatever you suspect import { withRenderDetective } from "react-render-detective"; const UserProfile = withRenderDetective(function UserProfile({ user, onSave }) { return /* … */; });
No server, no account, no API key, no browser extension.
▲ [RRD] UserProfile #47 Reason: prop changed (reference only) Changed: user Duration: 8.4ms
explain() aggregates every recorded render of one component into one answer.
> rrd.explain("ProductRow")
ProductRow
143 recorded renders
Why?
87% of renders followed `filters` changing by reference while its contents stayed the same.
Breakdown
props 124 87%
parent 15 10%
mount 1 1%
state-or-external 3 2%
Reference-only prop changes
filters 124 87% (object)
onSelect 118 83% (function)
Cost
average 12.4ms
total 1773.2ms
potentially avoidable 124 render(s), ~1537.6ms
Next step
Find where `filters` is created in ProductPage and stabilise it
(useMemo, or pass the primitive fields you use).
Confidence: high
React.memo is suggested only when the evidence supports it, with the cost shown so you can decide. Never blanket advice.
Cause could not be determined reliably. — rather than inventing a cause.
Every diagnosis carries high / medium / low confidence,
and the limitation behind it is printed with it.
| Question | Answer |
|---|---|
| What rendered? | component, render number, mount vs update |
| Why? | props · parent · context · state · external store — with the evidence |
| What changed? | per prop: a real value change vs a reference-only change |
| Where from? | the nearest instrumented ancestor, and whether it re-rendered |
| How expensive? | subtree duration, and self duration with instrumented descendants subtracted |
| How sure are we? | high / medium / low, on every single diagnosis |
| What next? | an evidence-based suggestion — or nothing at all |
Measured against the built package. Full method and caveats in BENCHMARKS.md.
| Instrumented share of a 2 000-component tree | Baseline | Instrumented | Overhead |
|---|---|---|---|
| 2% | 35.02 ms | 36.89 ms | +5.3% |
| 10% | 36.22 ms | 38.34 ms | +5.9% |
| 50% | 35.55 ms | 49.42 ms | +39.0% |
Roughly 10 µs per instrumented component, flat with tree size — against the 0.1 ms target, that is about 8× headroom. Percentage overhead is a function of how many components you wrap and how much work each does: wrapping every trivial leaf in a big tree is expensive, and the benchmark says so rather than quoting only the flattering number.
This package uses no private React APIs. That costs it some things, and every one of them is written down.
state-or-external — we say we cannot tell which._debugSource was removed in React 19), so they are not in v1.The full feasibility report classifies every feature as reliable, inferred, or impossible without internals — written before any of it was built.