"Error: Invalid hook call." when importing React component from shared component library in monorepo (turborepo & npm workspaces)
Asked Answered
V

1

7

I've been setting up a monorepo with the goal of creating a shared component library to use for several different Next.js apps (following this example) but whenever I import a component from the shared library I get

Unhandled Runtime Error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
  1. The hook (useState) is being called inside the body of a function component. The component works fine if I import it from the same folder as the project
  2. Again, it works fine locally
  3. As far as I can see I only have one version of react & react-dom at the root of my monorepo, both v. 17.0.2. react & react-dom are set as peerDependencies in my shared component package

I think it might have something to do with the mui components I'm using inside my component because this component can be imported and used no problem:

    // TestComponent.jsx

    import * as React from 'react';

    export const TestComponent = () => {
      return <h1>test</h1>;
    };

but this one throws the invalid hook error:

    // TestButton.jsx

    import * as React from 'react';
    import { Button } from '@mui/material';

    export const TestButton = () => {
      return <Button>Boop</Button>;
    };

Here's the package.json for my shared components package:

{
  "name": "shared-components",
  "version": "0.0.0",
  "main": ".dist/index.js",
  "module": "./dist/index.mjs",
  "sideEffects": false,
  "license": "MIT",
  "files": [
    "dist/**"
  ],
  "scripts": {
    "build": "tsup src/index.jsx --format esm,cjs --external react",
    "dev": "tsup src/index.jsx --format esm,cjs --watch --external react",
    "lint": "TIMING=1 eslint src --fix",
    "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
  },
  "devDependencies": {
    "tsup": "^5.10.1"
  },
  "peerDependencies": {
    "@mui/icons-material": "^5.6.1",
    "@mui/material": "^5.6.1",
    "@mui/utils": "^5.6.1",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  }
}

And I've added this to my next.config.js of the project I'm trying to use the shared components:

const withTM = require('next-transpile-modules')(['shared-components']);

module.exports = withTM({
  reactStrictMode: true,
});
Valine answered 24/4, 2022 at 17:41 Comment(0)
V
0

Finally solved this...

I had to add this to my next.config.js in my consumer site

const withTM = require('next-transpile-modules')(['shared-components'], { resolveSymlinks: false });

module.exports = withTM({
  reactStrictMode: true,
});

Even though the documentation for next-transpile-modules says for npm workspaces resolveSymlinks should be left as true by default, setting it to false was the only thing that worked for me.

If anyone can clarify to me why this works I'd be happy to learn but for now it's working as expected.

Valine answered 25/4, 2022 at 5:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.