12

I've been trying to create my own Component Library for the past few weeks, everything is going smooth except from the fact that I can't get hooks to work. Whenever I import from the library a component that uses hooks I get "Hooks can only be called inside the body of a function component."

https://reactjs.org/warnings/invalid-hook-call-warning.html

I would expect it's something about React being duplicated which I don't see when I run npm ls react.

This is my webpack.config.js

const path = require('path');
var nodeExternals = require('webpack-node-externals');
module.exports = {
 entry: './src/index.js',
 module: {
  rules: [
   {
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
     loader: 'babel-loader',
    }
   },
   {
    test: /\.scss$/,
    sideEffects: true,
    use: [
     { loader: 'style-loader' },
     { loader: 'css-loader' },
     { loader: 'sass-loader' },
    ],
   },
   {
    test: /\.(png|gif|jpg|svg)$/,
    use: {
     loader: 'url-loader',
     options: {
      limit: 50000,
     },
    },
   },
   {
    test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
    use: [
    {
     loader: 'file-loader',
     options: {
     name: '[name].[ext]',
     outputPath: 'fonts/'
     }
    }
    ]
   }
  ],
 },
 resolve: {
  extensions: ['.scss', '.ttf', '.js', '.json', '.png', '.gif', '.jpg', '.svg'],
  },
 output: {
  path: path.resolve(__dirname, 'dist/'),
  publicPath: '',
 filename: 'index.js',
  libraryTarget: 'umd',
 },
 target: 'node',
   externals: [ nodeExternals() ]
};

This is my package-json

{
  "name": "ui-components",
  "version": "1.1.1",
  "description": "componentes UI",
  "main": "dist/index.js",
  "scripts": {
    "test": "jest",
    "build": "webpack --mode production",
    "storybook": "start-storybook -p 9001",
    "docz:dev": "docz dev",
    "docz:build": "docz build"
  },
  "repository": {
    "type": "git",
    "url": "..."
  },
  "keywords": [
    "react",
    "shared",
    "components",
    "botbit"
  ],
  "author": "Uriel Chami",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.2.2",
    "@babel/preset-env": "^7.2.3",
    "@babel/preset-react": "^7.0.0",
    "@storybook/addon-actions": "^5.0.0",
    "@storybook/addon-storyshots": "^5.1.10",
    "@storybook/react": "^5.0.0",
    "@tulipjs/eslint-config": "^1.1.1",
    "babel-loader": "^8.0.1",
    "babel-plugin-macros": "^2.6.1",
    "css-loader": "^1.0.0",
    "docz": "^1.2.0",
    "docz-theme-default": "^1.2.0",
    "file-loader": "^4.1.0",
    "jest": "^24.8.0",
    "node-sass": "^4.9.3",
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "react-router-dom": "^5.0.1",
    "react-test-renderer": "^16.8.6",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "webpack": "^4.28.3",
    "webpack-cli": "^3.2.0",
    "webpack-node-externals": "^1.7.2"
  },
  "peerDependencies": {
    "react": "16.9.0",
    "react-dom": "16.9.0"
  },
  "dependencies": {
    "mdbreact": "..."
  }
}

Im using npm link to test the functioning of the library but also I can deploy it to GitHub and npm update it.

I've added window.React1 = require('react'); in node_modules/react-dom/index.js (in the App that uses my library). And

  require('react-dom');
  window.React2 = require('react');
  console.log(window.React1);
  console.log(window.React1 === window.React2);

in my App.js (in the app that uses my library).

And it throws true. I'm slowly getting clueless. Any ideas?


Edit:

The code where I'm trying to use Hooks is:

import React, {useEffect} from 'react';

export const Test = () => {
    useEffect(()=>{
        console.log("component did mount");
    } , [])
    return(<div>Hola!</div>);
}

Edit 2

When I build yarn webpack --mode development it throws 'require is not defined'.

Here it goes a GitHub repo with my complete code if anyone wants to toy around: https://github.com/uchami/hooks-not-working


Edit 3

I add the component library (ui-components) as so yarn add git+ssh:repository. The application where I import my library is simply a plain create-react-app.

And I just implement the Test component (which is the one that uses Hooks) using import {Test} from 'ui-components' and I use it just like that <Test></Test>.

The compilation of the ui-components project is just yarn build on the console. On the background what is doing is calling yarn webpack --mode production. If you call yarn webpack --mode development you will see extra verbose error output.

Uriel Chami
  • 418
  • 3
  • 13
  • Can you supply the code where you are attempting to use hooks? – Matt Oestreich Aug 12 '19 at 03:36
  • Following as I also have this issue when using hooks in components written in our component library and using storybook. – Drew Reese Aug 12 '19 at 05:27
  • Simplest Hooks code in the world. Let me edit the post. – Uriel Chami Aug 12 '19 at 07:41
  • Yes @DrewReese, I'm also using storybook – Uriel Chami Aug 12 '19 at 10:59
  • Checking the linked repo I see you are using `.js` extension in files that should be `.jsx`. Your issue might be different, but I suggest updating those (and possibly updating the webpack config) to see if it fixes it, or gives a different error. – Alvaro Aug 12 '19 at 18:35
  • @Alvaro, will do, I don't think it should make any difference, but lets see – Uriel Chami Aug 12 '19 at 22:43
  • @UrielChami the hook and all the other errors do come from the app using your library. So it would be very helpful, if you also show use the code of the app and how you build it. I could imagine some causes, but your library seems OK so far... – ford04 Aug 14 '19 at 15:50
  • @ford04 post edited. Edit 3 has the extra information you're asking for. Hope it helps to get to the solution. – Uriel Chami Aug 14 '19 at 16:01
  • I still do not get it... if you get the "require is not defined" error in your app build, did you eject the app? Or why do you use the webpack command for CRA, that makes the build process transparent (`yarn build`)? If it happens in your library I cannot follow - I can build your library with development and production mode without errors. I also do not understand the relation between your two errors you mentioned- the one with required undefined in development (it works in production?) and your hook error. – ford04 Aug 14 '19 at 16:48
  • @ford04 My issue is: My library compiles perfectly, even with hooks. But when I implement my library in any react app, the component that actually USE hooks, will fail and tell me that I'm violating React invariant. – Uriel Chami Aug 14 '19 at 18:38
  • If that's the problem, can you share an example of a React app failing to implement this library? If the issue is that the library works until it's installed in another context, knowing that context would be helpful in assessing the issue. – Mickey Aug 15 '19 at 22:29
  • @UrielChami Can you try out my answer down under? Let me know, if one of the solutions works out for you. – ford04 Aug 17 '19 at 16:35
  • I will, on Monday. It seems to work, but I'll let you know an marked as answered, and also, of course, release the bounty. – Uriel Chami Aug 18 '19 at 04:02

4 Answers4

28

"Hooks can only be called inside the body of a function component."

Let's rule out all possibilities according to React docs:

  1. You might have mismatching versions of React and 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.

1.) If unsure, try to reinstall react and react-dom in the latest version in your app, so they 100% match (or look at your package-lock.json / yarn lock):

npm i react@latest react-dom@latest

2.) Your useEffect Hook usage inside Test looks fine - Test is a function component and useEffect is called at the top-level.

3.) It would be my main guess for the error at hand.

You are using npm link to link the library for local tests. npm link will add a symbolic link for the complete library working directory (including node_modules) inside app project's node_modules. react is installed both as devDependencies in the library and as dependencies in the app project.

Now, when Webpack bundles the app project, it will choose react either from library or app - whichever is included in the nearest node_modules folder relative to the importing module.

Solution

A fix is mentioned on the Invalid Hook Call Warning page:

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.

Alternative: Tell Webpack to always resolve to a single react package - the one in your app node_modules (credit goes to these two issues). webpack.config.js:

resolve: {
  ...
  alias: {
    // Needed when library is linked via `npm link` to app
    react: path.resolve("./node_modules/react")
  }
}

"require is not defined"

This likely happens, when you run the web app bundled by a faulty Webpack config, where require calls are not dropped and replaced by the actual code modules.

If the application project is based on create-react-app, you might import the library in app and just run start or build. Given a custom Webpack config (e.g. via eject), run the build with target:web (or omit target) and leave out externals: [ nodeExternals() ].

ford04
  • 66,267
  • 20
  • 199
  • 171
  • 3
    "You said, you npm link your library for local tests. npm link will add a symbolic link for the complete library working directory (including "node_modules")" this was the most helpful of the whole tips, but all the answer seems very reasonable, I've been trying all those solutions out. Thanks for the follow up and the feedback – Uriel Chami Aug 20 '19 at 18:21
  • 1
    You made my day with the npm link tip. Thank you very much. – Jumi Mar 11 '21 at 22:23
1

Is it just that you're trying to use your library in React apps running older versions of React that aren't compatible with hooks?

I just made a new create-react-app, added your repo as a dependency with yarn add https://github.com/uchami/hooks-not-working, and had no problem adding the Test component to the default App.js.

I only got an error after rolling back my app's version of React.

Mickey
  • 570
  • 3
  • 11
1

This might help too: React hooks duplicate react package

I had the very same problem but even worse, I have to manage multiple apps with their own libs.

The solution proposed solves all 'multiple react libs' errors

Dany Dhondt
  • 881
  • 9
  • 27
-1

Have you tried changing your target from node to web? That may cause some issues.

Also don't know all the details behind where you plan to use your library but something as simple as babel ./src --out-dir ./core --copy-files can be useful when you are using your library in an environment where you already have a build step that handles bundling.

I'd add a comment, but uh rep.

apachuilo
  • 341
  • 3
  • 6