Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid hook call on npm module

First of all link to the repo: https://github.com/vmarchesin/react-konami-code

You should be able to build it (make sure to uncomment the export on src/index.js) and link to your project. It is also possible to try it out using npm i -S [email protected] and get the error for the hook. I removed the hook from 2.0.0-beta.1 because it's broken.

Description of the problem

I created a custom hook for my npm module and it doesn't work after being published or using it as a module. I suspect the issue is with how webpack bundles it but I can't solve it.

Steps taken

  • I made sure to declare react and react-dom as externals in my webpack config.
externals: [
  {
    react: {
      root: 'React',
      commonjs2: 'react',
      commonjs: 'react',
      amd: 'react',
    },
    'react-dom': {
      root: 'ReactDOM',
      commonjs2: 'react-dom',
      commonjs: 'react-dom',
      amd: 'react-dom',
    },
  },
],
  • Declared react and react-dom as peerDependencies in the package.json
"peerDependencies": {
  "react": "^16.13.1",
  "react-dom": "^16.13.1"
},
  • The hook is working. If I declare it and use it, it works. If I import it from the module (or use npm link) it doesn't. Here is the code for the hook:
import { useEffect, useState, useCallback } from 'react';

export default (action, {
  code = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65],
} = {}) => {
  const [input, setInput] = useState([]);

  const onKeyUp = useCallback(
    (e) => {
      const newInput = input;
      newInput.push(e.keyCode);
      newInput.splice(-code.length - 1, input.length - code.length);

      setInput(newInput);

      if (newInput.join('').includes(code.join(''))) {
        action();
      }
    },
    [input, setInput, code, action],
  );

  useEffect(() => {
    document.addEventListener('keyup', onKeyUp);
    return () => {
      document.removeEventListener('keyup', onKeyUp);
    };
  }, [onKeyUp]);
};
  • I am not calling the hook inside a class component.

I am using [email protected] as well as babel@7 and [email protected].

Here is how I export the index file for the webpack build:

import Konami from './Konami';

export default Konami; // disregard this, it works
export { default as useKonami } from './useKonami'; // this doesn't work

Here's a screenshot and transcript of the error:

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
like image 869
vmarchesin Avatar asked May 12 '20 10:05

vmarchesin


People also ask

Where do I call React Hooks?

Only Call Hooks at the Top Level Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.

Can I use Hooks in class component?

You can't use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component.

What is a hook call React?

Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don't work inside classes — they let you use React without classes. (We don't recommend rewriting your existing components overnight but you can start using Hooks in the new ones if you'd like.)


2 Answers

I had the same error message while trying to import a react component from a npm package that I built. As the explanation of the error message suggests, this was propably a case of a duplicate React.

The important part concerning the error message is to bundle react and react-dom as external imports, so that the react of the parent project is being used, instead of using a second, that is a duplicate React.

I solved that by building my npm package with rollup.js, following this guide basically.

My resulting rollup.config.js:

import pkg from './package.json'
import babel from '@rollup/plugin-babel';

export default {
  input: 'src/index.js',
  output: [
    {
      file: pkg.main,
      format: 'cjs',
      exports: 'named',
      sourcemap: true,
      strict: false
    }
  ],
  plugins: [babel({ babelHelpers: 'bundled' })],
  external: ['react', 'react-dom']
}

You need the following dev-dependencies in your npm package (assuming you also have to transpile with babel):

npm i -D rollup babel-core @rollup/plugin-babel

You can bundle with rollup like this:

// add that to your package.json
"scripts": {
  "build": "rollup -c",
  "start": "rollup -c -w"  // for watching changes
},

With npm link it is even possible to get a hot-reload in the parent project for the packaged react component you are developing, without any npm publishing. Yay.

like image 120
anarchist912 Avatar answered Nov 03 '22 12:11

anarchist912


I had the same issue, tried all the solutions and none of them worked.

The solution to this is, we need to configure Webpack to essentially ignore React as part of the production bundle along with peer dependency.

To do this, add below lines in webpack.config.js:

externals: {
   "react": "commonjs react",
   "react-dom": "commonjs react-dom",
}
like image 31
Vikash Kumar Avatar answered Nov 03 '22 10:11

Vikash Kumar