You've seen this error. npm install fails with ERESOLVE, you search it, find a GitHub issue, copy an overrides snippet into package.json, and the red text disappears on your next run.
Nobody asks what that block actually did.
I hit this exact error yesterday setting up a cloned repo, and instead of moving on once the install went green, I went and looked. Full video below, written breakdown after it for anyone who'd rather read.
The error
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: forced-error-demo@1.0.0
npm error Found: react@19.2.8
npm error node_modules/react
npm error react@"^19.0.0" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^16.9.0 || ^17.0.0" from @testing-library/react-hooks@8.0.1
Two lines in here matter, and they're not making the same kind of claim.
Found: react@19.2.8 — this is just what's already resolved, because it's what my own package.json asked for (^19.0.0).
peer react@"^16.9.0 || ^17.0.0" from @testing-library/react-hooks@8.0.1 — this is a different thing entirely: a peer dependency.
What a peer dependency actually is
Straight from @testing-library/react-hooks's own package.json:
{
"name": "@testing-library/react-hooks",
"version": "8.0.1",
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0"
}
}
A peerDependencies entry is a dependency the package needs, but expects the consumer to provide — the package isn't bundling its own copy of React, it's reaching into whatever React is already running in your project, and declaring the only versions it was built to reach into safely.
Compare that to a normal dependencies entry, which npm installs as its own separate copy in node_modules. A peer dependency installs nothing. It just checks.
npm has enforced that check by default since v7. Before that, npm 3 through 6 mostly let mismatches like this through silently — which is exactly why the --legacy-peer-deps flag is named what it is: it tells npm to go back to that old, unchecked behavior.
So this error isn't npm being broken. It's npm accurately reporting that a package in your tree is making a claim about your React version that isn't true.
The fix everyone copy-pastes
Here's the snippet you'll actually find on GitHub issues for this exact situation:
"overrides": {
"@testing-library/react-hooks": {
"react": "$react"
}
}
The $react syntax means "whatever version I already have installed as a direct dependency, use that." So this line says: wherever @testing-library/react-hooks declares a requirement on react — including its peer requirement — force it to match root's react@19.0.0 instead.
Run npm install again. It succeeds. No red text. Looks fixed.
What actually changed (and what didn't)
overrides didn't touch a single line of code inside @testing-library/react-hooks. It didn't check whether that package's internals can actually run against React 19. It went into the resolved dependency graph and rewrote the version number the peer check compares against — from ^16.9.0 || ^17.0.0 to 19.0.0 — so the check has nothing left to disagree with.
That's the entire mechanism. overrides doesn't resolve incompatibility. It rewrites the number two packages are being compared on, whether or not the code underneath agrees.
What did not happen:
- No code inside the overridden package ran
- No check that its React-16/17-era internals still work against React 19's scheduling model
- No test suite executed to confirm any of it
Why the gap matters
@testing-library/react-hooks was built before hook-testing support existed in @testing-library/react itself, and it's effectively frozen at how React 16 and 17 scheduled renders. React 19's internals have moved since then. The override doesn't close that gap — it just moves when you find out about it.
Without the override: you read two lines of log, thirty seconds, and you're acting on real information.
With the override: install succeeds clean, ships to CI, ships to prod — and the actual discovery happens whenever a hook test misbehaves or a render warning shows up with no line in your own code pointing back to it. Same incompatibility, if it's real. Just a much more expensive place to find it.
What to check before you reach for overrides
In order:
-
Does the conflicting package still need to exist? In this case — no.
@testing-library/reacthas supported hook testing natively for a long time now, which makes@testing-library/react-hooksa dependency that's outlived its reason for being. Deleting it is a better fix than patching around it. - Can it be upgraded instead? A newer major of the conflicting package may not have this peer conflict at all. You might have to touch your own code too, since not every upgrade is backward-compatible — but that's still cheaper than a silent runtime bug.
- If it's genuinely unavoidable, scope the override. Don't apply it globally:
// unscoped — forces every package in the tree that touches react
"overrides": {
"react": "$react"
}
// scoped — forces it only where the actual conflict is
"overrides": {
"@testing-library/react-hooks": {
"react": "$react"
}
}
Everything else in your tree keeps the version npm originally resolved. Smaller blast radius, easier to reason about, easier to remove later.
A temporary patch. Not permanent config.
If you do add an override, treat it like debt, not like settings. Leave a note on why it's there — package.json doesn't support comments, so put it in a README or an OVERRIDES.md: what it's patching, when to revisit it, who added it. Put it on your dependency-audit schedule, whatever that already is for your team, and pull it out the moment the upstream package makes it unnecessary.
An override with no expiry date doesn't leave. It just becomes the next person's mystery to reverse-engineer.
If this was useful, the video covers the same ground with the terminal running live — installing with and without the override, reading the actual npm ls output, watching what changes. Drop a comment if you want the follow-up on npm's resolution algorithm itself — why these conflicts happen before any override enters the picture.
Top comments (0)