Invalid hook call on npm-link library

13.8k views Asked by At

Issue description:

  • I'm currently authoring a package called eformless

  • I've used CRA to create a directory called sandbox, where I linked the package.

  • I keep getting the following error, when trying to launch the sandbox react app with the linked package that I'm trying to test:

    // inside my 'sandbox' App.tsx
    import React from 'react'
    import { useField } from 'eformless'
    
    const App = () => {
      const [test, handleChange] = useField('testing')
      return (
        <input type="text" value={test.value} onChange={handleChange} />
      )
    }
    

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 altered link due to stackoverflow ToS for tips about how to debug and fix this problem.

Where the issue occurs:

The issue seems to point to my library, where I have written a custom hook that uses hooks imported from React.
Here's the problematic excerpt from my code:

export const useField = <T>(
  input: T
  ...checkFunctions: Array<(...args: unknown[]) => unknown>
): [FieldType<T>, FieldHandlerFunction<T>, FieldHandlerFunction<T>] => {
  const [value, setValue] = useState(input)
  const [name, setName] = useState('')
  // Issue seems to be here ^
}

My setup was fairly normal and seems to work fine in other projects:

  • First I bundled my library via rollup -c
  • Created a link inside my eformless library folder via npm link
  • And created the symlink inside my sandbox folder via npm link eformless

What I tried:

I naturally tried to fix the issue via the provided link in the error message. This appears to be an issue when linking your own library, as mentioned here

This problem can also come up when you use npm link or an equivalent. In that case, your bundler might “see” two Reacts — one in application folder and one in your library folder. Assuming myapp and mylib are sibling folders, one possible fix is to run npm link ../myapp/node_modules/react from mylib. This should make the library use the application’s React copy.

I did run the npm link ../sandbox/node_modules/react in my eformless folder, however this did not seem to help, even after closing node server the and recompiling.

Either way it seems really confusing to me, because runnign npm ls react only shows a single version of react and it should be valid, given the useState is used top-level inside a custom hook.

Reproducing the issue

Both repositories are public

6

There are 6 answers

5
Benjamin On BEST ANSWER
  1. You might have more than one copy of React in the same app See altered link due to stackoverflow ToS for tips about how to debug and fix this problem.

It is because you have react in your devDependencies for eformless. This causes your application to run two instances of React.

Instead, move react to peer dependencies in eformless package.json, delete your node_modules directory and run npm install.

By adding react to peer dependencies, you are informing your module bundler not to include react, but instead to expect that react will be made available to it by the dependent application.

"peerDependencies": {
  "react": "^16.13.1"
}

Also, I noticed that you had checked in your node_modules directory into eformless. You should not do that. Instead, add node_modules to your .gitignore file.

0
himanshu On

Use the alias in your main project. Then for your library, it will use the alias of your main project.

react : path.resolve(__dirname, './node_modules/react')
0
morganney On

Another option is to use a webpack alias inside the dependent app's build to dedupe the react dependencies.

Inside webpack.config.js for app consuming linked dependency:

resolve: {
  alias: {
    react: path.resolve(__dirname, '<app_path>/node_modules/react'),
    'react-dom': path.resolve(__dirname, '<app_path>/node_modules/react-dom')
  }
}
3
Gerardo Roza On

Just in case it's useful for others, I was already using react as a 'peer dependency' in my library, and still getting this issue.

I had to perform the following steps to solve it:

In the library:

  1. If you haven't done it already, setup the libraries that are generating issues as peerDependencies in the package.json instead of dependencies or devDependencies, e.g. in my case react:
"peerDependencies": {
  "react": "^16.8.6",
  ...
}
  1. run npm install
  2. build the library (in my case, with a rollup -c npm script)

In my main app:

  1. change the version of my library to point to my local project with a relative path in package.json, e.g.
"dependencies": {
  "my-library": "file:../../libraries/my-library",
  ...
}
  1. Add resolve.symlinks = false to my main app's webpack configuration

  2. Add --preserve-symlinks-main and --preserve-symlinks to my package.json start script, e.g:

"scripts": {
  "build": "set WEBPACK_CONFIG_FILE=all&& webpack",
  "start": "set WEBPACK_CONFIG_FILE=all&& webpack && node --preserve-symlinks-main --preserve-symlinks dist/server.js",
}
  1. run npm install
  2. run npm run start
0
Farooq Alaulddin On

I just ran into the same problem. My case was similar to what Benjamin said. Clear any version of react you have in the package.json lib then re install packages. When I came to link my library react to a testing app, I would get Cannot read properties of null (reading 'matches'). I fixed this by linking react-dom then react from the testing app, so:

from lib:

  • npm link ../myapp/node_modules/react-dom
  • npm link ../myapp/node_modules/react
0
Paul John Butad On

If @Benjamin's answer didn't solve the issue which is moving react and react-dom from your dependencies to peerDependencies.

Hang on there, the only missing steps are:

  1. Delete node_modules and run npm install --legacy-peer-deps. This will not include react and other packages in your peerDependencies - solves the issue of installing multiple react.

  2. Now, you still can't run your test because jest wouldn't find those module as they aren't installed in your library. What you can do is to map those "react imports" in your library to the react installed in your main app.

  • In your main app's jest config:
moduleNameMapper: {
    "^react$": "<rootDir>/node_modules/react",
    "^react-dom$": "<rootDir>/node_modules/react-dom",
    ...other packages you have in your peerDependencies
}
  1. Try running your tests again and it should work.

Take note: If you have linked your library's react already with npm link, make sure you undo it by executing npm rm --global react, then reinstall your packages in your main app