How to Fix React-DOM Version Mismatch Errors Crashing npm start
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke:
reactandreact-domare pinned to different major or minor versions in yourpackage.jsonor a transitive dependency is pulling in a conflicting peer, causing the React runtime to refuse initialization. - How to fix it: Align
reactandreact-domto the same explicit version, audit peer dependency requirements of third-party packages, and lock the tree withpackage-lock.jsonoryarn.lock. - Shortcut: Use our Client-Side Sandbox below to auto-refactor this — paste your
package.jsonand get a corrected diff without sending your config to a third-party server.
The Incident (What Does the Error Mean?)
You run npm start and the process dies immediately. The raw terminal output looks like one of these:
ERROR in ./node_modules/react-dom/cjs/react-dom.development.js
Module not found: Error: Can't resolve 'react' in '.../node_modules/react-dom'
// OR the more explicit peer conflict:
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR! react@"^18.2.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^17.0.0" from [email protected]
npm ERR! node_modules/react-dom
npm ERR! react-dom@"^17.0.2" from the root project
// OR at runtime after webpack compiles:
Warning: Invalid hook call. Hooks can only be called inside of a function component.
Uncaught Error: Cannot read properties of null (reading 'useState')
Immediate consequence: The dev server never starts. In CI, the build job fails at the npm install or npm run build step, blocking every PR merge. If this slips into a production Docker image built with --legacy-peer-deps, you get a broken runtime that throws cryptic hook errors instead of a clean install failure.
The Attack Vector / Blast Radius
This is not a security exploit in the classical sense — but the blast radius is wide:
Duplicate React instances in the bundle. When webpack resolves two different
react-domversions, it may bundle both React runtimes. React's internal singleton state (__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) gets split across instances. Every hook call (useState,useEffect,useContext) throws or silently corrupts state. This is a silent runtime bomb — tests may pass while the app is broken for end users.Transitive dependency cascade. A single UI library (e.g.,
@mui/[email protected]requiringreact@^18, while your project hasreact-dom@17) can detonate the entire dependency tree. Runningnpm installwith--legacy-peer-depspapers over the conflict but ships broken code.CI/CD pipeline paralysis.
ERESOLVEerrors are hard failures. Every engineer's branch, every preview deployment, and every staging build is blocked until the rootpackage.jsonis corrected.Lock file drift. If
package-lock.jsonwas committed while--legacy-peer-depswas active, the conflict is invisible locally but explodes in clean CI environments that run strictnpm ci.
How to Fix It (The Solution)
Step 1 — Diagnose the exact conflict
# Show the full peer dependency tree and flag conflicts
npm ls react
npm ls react-dom
# For npm 7+, get a clean conflict report
npm install --dry-run
Identify: (a) what version of react is in root, (b) what version of react-dom is in root, (c) which third-party package is pulling a mismatched peer.
Basic Fix — Align react and react-dom versions
// package.json
"dependencies": {
- "react": "^18.2.0",
- "react-dom": "^17.0.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"react-scripts": "5.0.1"
}
After editing:
# Delete the corrupted lock file and node_modules
rm -rf node_modules package-lock.json
npm install
npm start
Enterprise Best Practice — Pin exact versions, use overrides, enforce in CI
For monorepos or projects with stubborn transitive conflicts, use npm overrides (npm 8.3+) or yarn resolutions to force a single React version across the entire tree.
// package.json
"dependencies": {
- "react": "^18.2.0",
- "react-dom": "^17.0.2",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
"react-scripts": "5.0.1"
},
+ "overrides": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ }
For Yarn Berry (v2+):
// package.json
+ "resolutions": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ }
For pnpm:
// package.json
+ "pnpm": {
+ "overrides": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ }
+ }
After applying overrides:
rm -rf node_modules package-lock.json
npm install
# Verify no duplicate React instances exist
npm ls react | grep -v deduped
# Should return ONLY one version line
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Enforce peer dependency validation in your pipeline
Add this as a required CI step before the build step:
# .github/workflows/ci.yml
- name: Validate dependency tree
run: |
npm install --dry-run 2>&1 | tee /tmp/npm-dry-run.log
if grep -q 'ERESOLVE\|peer dep' /tmp/npm-dry-run.log; then
echo "::error::Peer dependency conflict detected. Fix package.json before merging."
exit 1
fi
2. Use npm ci — never npm install — in CI
# Dockerfile or CI script
- RUN npm install
+ RUN npm ci --ignore-scripts
npm ci requires a valid package-lock.json and fails on any tree inconsistency. It will catch this class of error on every build.
3. Add a check-peer-deps pre-commit hook
# Install the tool
npm install --save-dev check-peer-dependencies
# package.json scripts
"scripts": {
"check-peers": "check-peer-dependencies"
}
Wire it into Husky:
// .husky/pre-push
npm run check-peers
4. Renovate Bot / Dependabot with grouped React updates
Configure Renovate to always update react and react-dom together in a single PR, preventing them from drifting:
// renovate.json
{
"packageRules": [
{
"matchPackageNames": ["react", "react-dom", "@types/react", "@types/react-dom"],
"groupName": "React core",
"groupSlug": "react-core"
}
]
}
5. Add a Webpack bundle duplicate check (runtime safety net)
npm install --save-dev duplicate-package-checker-webpack-plugin
// webpack.config.js or craco.config.js
const DuplicatePackageCheckerPlugin = require('duplicate-package-checker-webpack-plugin');
module.exports = {
plugins: [
new DuplicatePackageCheckerPlugin({ strict: true })
]
};
This fails the webpack build if duplicate react instances are detected in the bundle — your last line of defense before a broken artifact ships.