React Library and Peer Dependency Woes

Building and consuming React libraries have a less than optimally documented trouble... peer dependencies. Let's discuss how to solve that!

🍿 3 min. read

You've been building your React component library, perhaps a UI Design System or a useful component you wish to share with the world. Your tests pass and it works great! Then, you try consuming your component in another React app and suddenly you're met with the following error:

Error: Invalid hook call. Hooks can only be called inside of the body of a function.

Worse yet is the suggested documentation page suggests three very different possible reasons this error could occur. I'm here to say that so long as you're using hooks appropriately, the trouble lies in React peer dependencies resulting in you unknowingly having multiple versions of React.

How to fix this

Whether you've set up your React library with create-react-library or create-react-app the first thing to check is your own ./package.json. If you see react or react-dom inside of your dependencies or devDependencies then you've already found the issue!

Ensure react and react-dom only exist within a peerDependnecies block.

You'll also want to manually remove them from the ./node_modules directory in case they exist already.

rm -rf node_modules/react node_modules/react-dom

At this point your issue is likely solved in the consuming app.

Fixing the fix... running tests and developing the addon locally

Okay great, if this works then you'll soon realize that you're unable to run tests or otherwise develop your addon locally at all because you need react and react-dom. The unfortunate reality is that you'll need to be intentional about when you install peer dependencies depending on what you're doing.

npm i --no-save react react-dom # this will not affect your package.json, and they may be auto-removed on the next npm install or yarn run

If you're finding this annoying, you may opt to add some scripts to your ./package.json to make installing and removing peer dependencies really easy. You may also then call them as prerequesit commands to your other standard commands.

  scripts: {
    "peers:install": "npm i --no-save react react-dom",
    "peers:remove": "rm -rf node_modules/react node_modules/react-dom",

"prebuild": "npm run peers:remove", "build": "echo pretend this command built your library",

"pretest": "npm run peers:install", "test": "echo pretend this command built your library", } }

Still having trouble? Check your sub-dependencies

Even after all of the above work I still ran into trouble. It turns out, this issue flows all the way down to literally any additional copy of react or react-dom from any dependency in the chain. I discovered this by wiping out my dependencies in large sections until my library successfully compiled for the consuming app, and then proceeded to narrow it down.

For me, I discovered that storybook-mobile, an optional storybook addon I was using, was having the very same issue of including react and react-dom in its devDependencies. It's an easy issue to miss, so I wrote up a detailed issue and submitted a PR to fix it. Thankfully, this turned out to be a success story and the latest release works wonderfully.

So in conclusion, be super careful that there aren't more than one react or react-dom dependencies anywhere at all in your dependency tree besides in your main app. Only one copy may exist anywhere at any time.