React 19 has introduced features like Server Components, Turbopack, and Concurrent Rendering. While these innovations enhance performance, they’ve also led to various peer dependency conflictsacross the ecosystem. If you can’t live without these features, this article is a blended guide to resolving the conflicts without resorting to using --force, --legacy-peer-deps, or downgrading to React 18.
I first experienced this issue with Radix UI and ShadCN, which they addressed, but after some experimenting I found similar issues in libraries like @testing-library/react, react-scripts, eslint-plugin-react, react-redux, recharts, cmdk, and at one point react-dom. Assuming many of these will receive updates (like React Dom), so perform your own experiments.
Note, before diving in: The quickest solve is to use****Yarn or Vite, both of which are great. I’ll cover the other options below.
Understanding Peer Dependency Conflicts
Peer dependencies dictate which versions of a package (e.g., React) are compatible with other libraries. Conflicts typically arise when:
Outdated Libraries: Libraries declare compatibility only up to React 18.
Strict Version Constraints: Exact versions are required, disallowing React 19.
Multiple React Versions: Different packages install conflicting versions of React.
Stricter Package Manager Enforcement: npm 7+ enforces peer dependency rules more strictly.
These conflicts create installation errors or runtime issues like:Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
Example Scenarios
A known issue with create-react-app (CRA) occurs when using React 19. The default installation installs React 19, while some dependencies, like @testing-library/react, specify a peer dependency of react@”¹⁸.0.0". This leads to errors like npm ERR! peer react@"^18.0.0" from @testing-library/react@13.4.0.
Attempts to resolve this with — legacy-peer-deps or by specifying a script version like npx create-react-app myprog — scripts-version react-scripts@5.0.1 . still result in conflicts.
Side Note: If you’re cool with React 18 install it within your project before running create-react-app.
npm install react@18 react-dom@18
npx create-react-app myprogHow to Identify Dependency Conflicts
Here’s how to pinpoint which packages are causing peer dependency conflicts:
1. Run npm ls to View the Dependency Tree
The npm ls command shows your project’s dependency tree, helping identify conflicting packages
This will display the versions of React used by different packages and highlight conflicts with UNMET PEER DEPENDENCY errors.
2. Use npm install to Check for Conflicts
Attempting to use npm install or running npm update will often reveal peer dependency conflicts:
If there are conflicts, npm will display warnings like:npm WARN some-package@1.0.0 requires a peer of react@¹⁸ but none is installed.
3. Inspect Conflicts Using npm outdated
The npm outdated command shows outdated dependencies and helps you identify potential conflicts with newer versions. Focus on packages related to React, as they are likely the source of conflicts.
4. Audit Dependencies with npm ls — depth=0
Use npm ls — depth=0 to list all top-level dependencies (those directly installed in your project). This will help you narrow down the packages to inspect further for peer dependency issues.
5. Check Dependency Versions in package.json
If the conflicting versions aren’t obvious, use cat package.jsonto inspect your package.json file. Then, compare the specified versions of React, React DOM, and other critical libraries with the required peer dependencies of conflicting packages.
6. Use npm dedupe to Detect Duplicates
Sometimes, conflicts occur due to duplicate React installations. Run the npm dedupe to deduplicate your node_modules. Then, re-run npm ls react to verify whether multiple React versions are installed.
7. Use yarn why
For Yarn users, yarn why provides detailed information about why a package is installed and which dependencies require it:
Resolving Peer Dependency Conflicts
Once you’ve identified the conflicts, any of these strategies can
1. Manually rollback the version in your project
Manually install compatible versions of conflicting packages using npm install @ , note this could cause issues with the package if it relies on features found in 19.
2. Use overrides (npm) or resolutions (yarn)
Force compatible versions without altering global settings forcing compatible versions without altering global settings in package.json and then run npm install.
// NPM overrides
{
"overrides": {
"react": "19.x",
"react-dom": "19.x"
}
}
// Yarn resolutions
{
"resolutions": {
"react": "19.x",
"react-dom": "19.x"
}
}3. Request Updates from Maintainers
Search the package’s repository for similar issues, write a ticket, and ask the maintainers for React 19 support. While waiting, apply other fixes temporarily.
4. Fork and Patch Outdated Libraries
If a critical library hasn’t updated its peerDependencies:
**Fork the library on GitHub.**Start by forking the library’s repository to your own GitHub account. This ensures you have full control over the code and can modify it as needed without affecting the original project.
Update the package to support React 19.• Open the package.json file in the forked repository.• Modify the peerDependencies section to include React 19.
Test and Verify Compatibility• Run the library’s test suite and insure they resolve. The Readme or package.json scripts section should identify how that’s done.
Publish Your Fork• Publish the updated package to your npm account under a scoped name, e.g.,
@your-username/Be a Good Code and Ecosystem Steward• Submit a well-documented pull request (PR) to the original repository.• Clearly explain the changes with “why”, “how” and “what”• Follow their contribution guidelines (generally in
/CONTRIBUTORS.md)
5. Switch to Compatible Alternatives
If an outdated package shows no sign of updating, replace it with a modern, actively maintained alternative. For example, in my case, I would use libraries like MUI or Chakra UI****instead of ShadCN.
6. Isolate Packages in a Monorepo
Use tools like Nx or Turborepo to structure your project in a monorepo, isolating conflicting packages in separate workspaces to minimize conflicts.
7. Custom Dependency Resolution Script
If you need to automate the process for installation, deployment, or modifying node_modules post-installation to resolve peer dependency conflicts:
Install conflicting packages.
Write a script to update peerDependencies in their package.json files.
Rebuild the project.
The script might look something like this:
// This script modifies the peerDependencies of conflicting packages directly within node_modules.
// Use Node.js to automate this process by adding "node resolve-peer-deps.js" to your package.json config
const fs = require('fs');
const path = require('path');
const packagesToUpdate = [
{
name: '@testing-library/react',
newPeerDependencies: {
react: '>=18.0.0 <20.0.0',
'react-dom': '>=18.0.0 <20.0.0',
},
},
{
name: 'another-library',
newPeerDependencies: {
react: '>=18.0.0 <20.0.0',
},
},
];
const resolvePath = (pkgName) => path.resolve('node_modules', pkgName, 'package.json');
const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'));
const writeJson = (filePath, data) => fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
const updatePeerDeps = ({ name, newPeerDependencies }) => {
try {
const packageJsonPath = resolvePath(name);
if (!fs.existsSync(packageJsonPath)) throw new Error(`Package not found: ${name}`);
const packageJson = readJson(packageJsonPath);
packageJson.peerDependencies = { ...packageJson.peerDependencies, ...newPeerDependencies };
writeJson(packageJsonPath, packageJson);
console.log(`[Updated] ${name} peerDependencies`);
} catch (error) {
console.error(`[Error] ${name}: ${error.message}`);
}
};
packagesToUpdate.forEach(updatePeerDeps);8. Monitor Updates with Renovate or Dependabot
Automate dependency updates with tools like Renovate, Dependabot, or my go-to npm-check-updates (ncu).
9. Just use — legacy-peer-deps and hang tight
Let’s face it: dealing with peer dependency conflicts is never fun but this isn’t a show stopper.
The key is experimentation. Not every project will need the same fix, try out a few of these approaches, see what sticks, and maybe even discover a trick or two along the way, save it in your gist or snippits, and call it a day.
Good luck!