facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
227.37k stars 46.37k forks source link

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: #15315

Closed sethandleah closed 5 years ago

sethandleah commented 5 years ago

Hi all, I am new to react and I am trying to create a react library of components and I came across this problem because one of the components I am creating uses REACT HOOKS.

Disclaimer: this is my first time creating an issue, so please bear with me.

So I am trying to create an accordion component which toggles between these classes accordion__item--open and accordion__item to open and close.

package.json

{
  "name": "react-lib",
  "version": "0.3.0",
  "description": "A simple UI library of react components",
  "main": "dist/index.js",
  "publishConfig": {
    "registry": ""
  },
  "scripts": {
    "login": "",
    "build": "webpack --mode=production",
    "develop": "webpack --mode=development --watch"
  },
  "repository": {
    "type": "git",
    "url": ""
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "homepage": "",
  "dependencies": {
    "@babel/core": "^7.3.4",
    "@babel/preset-env": "^7.3.4",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.5",
    "eslint": "^5.15.1",
    "eslint-loader": "^2.1.2",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.2.3",
    "webpack-node-externals": "^1.7.2"
  },
  "devDependencies": {
    "eslint-config-airbnb": "^17.1.0",
    "eslint-plugin-import": "^2.16.0",
    "eslint-plugin-jsx-a11y": "^6.2.1",
    "eslint-plugin-react": "^7.12.4",
    "eslint-plugin-react-hooks": "^1.6.0"
  }
}

webpack.config.js

const path = require('path');
const webpack = require('webpack');

module.exports =  {

  mode: 'development',
  optimization: {
    minimize: true,
  },
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index.js',
    library: '',
    libraryTarget: 'commonjs'
  },
  target: 'node',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env', '@babel/react']
        }
      }
    ]
  } 
};

This is the accordion container:

import React from 'react'; 

function Accordion({ children }) {

  return (
    <div className="accordion">
      { children }
    </div>
  ); 

}

export default Accordion;

This is the accordion item that will live inside the container:

import React, { useState } from 'react'; 

function AccordionItem({header, content}) { 

const [ isActive, toggleActive ] = useState(false);

return (
    <div className={ `accordion__item ${ isActive ? 'accordion__item--open' : '' }` }>

      <button
        className="accordion__item-header"
        onClick={ () => isActive ? toggleActive(false) : toggleActive(true) }
      >
       { header }
       <svg className="icon icon--debit" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
         <path className="icon__path" d="M22.37,22.33V2.67a2.63,2.63,0,0,1,5.26,0V47.24a2.63,2.63,0,0,1-5.26.09V27.58H2.71a2.63,2.63,0,0,1,0-5.25Zm11.92,5.25a2.63,2.63,0,0,1,0-5.25h13a2.63,2.63,0,0,1,0,5.25Z">
         </path>
       </svg>
     </button>

     <div className="accordion__item-content">
       { children }
     </div>

   </div>
 )

};

export default AccordionItem;

Now inside of a create-react-app I import these components

My library and the create-react-app are relative to each other and I am using npm link

import React from 'react'; 

import {AccordionItem} from 'react-lib'
import {Accordion} from 'react-lib';

function App ({props}) {

  return (

      <Accordion>
        <AccordionItem header={"Hello"} content={"World"}/>
      </Accordion> 
  )

}

export default App;

I have followed all of these instructions and I still get the same error.

Current behavior?

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://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

Steps to reproduce

Expected behavior

To see the above, do the following:

import { Accordion } from 'react-lib';
import { AccordionItem } from 'react-lib';

Versions of React, Browser / OS are affected by this issue:

jaisonjjames commented 4 years ago

I solve this by change

                <Route key={index} render={route.component} path={route.path} />

to

                <Route key={index} component={route.component} path={route.path} />

but I don't know why :(

Thanks, Dear, Its worked for me, Still, I am confused, why?

pluma commented 4 years ago

@jaisonjjames see my reply. There are some explanations on StackOverflow if you have trouble understanding the difference: https://stackoverflow.com/questions/48150567/react-router-difference-between-component-and-render

Rolando-Barbella commented 4 years ago

In my case, I had let match = useRouteMatch("/user/:id"); outside the component, which is a hook from react-router, so the error was right.

gabrielyangzon commented 4 years ago

from
<Route path="/component" render={myComponent} />

to <Route path="/component" component={myComponent} />

ammoradi commented 4 years ago

from

<Route path="/component" component={myComponent} />

to

<Route path="/component"><myComponent /></Route>

-_-

AbreezaSaleem commented 4 years ago

In my case I was referring to a library from the window object which had a different React version. I ended up using the externals configuration option from webpack which helped me refer to that library's react and react-dom and made the error go away.

So I believe the reason I was getting this error was because You might have more than one copy of React in the same app.

Myrdden commented 4 years ago

In my case I was referring to a library from the window object which had a different React version. I ended up using the externals configuration option from webpack which helped me refer to that library's react and react-dom and made the error go away.

So I believe the reason I was getting this error was because You might have more than one copy of React in the same app.

@AbreezaSaleem How did you accomplish this? I'm having the same issue, using a library which has react as an external is somehow showing up as a different version... I've tried externals: [ 'react' ], externals: { react: 'react' } and externals: { react: 'React' }, result is always the same...

JuanIrache commented 4 years ago

I created a somewhat minimal example to reproduce this. In my case the problem happens on ElectronJS with electron-webpack and react-dropzone

https://github.com/JuanIrache/dropzone-test

Yolantele commented 4 years ago

there is a nice webpack configuration solution to pointing to the same/single react instance. It was to add a resolutions inside the app that is using the 'to be npm module'.

inside webpack.config.js, add alias to react - so it uses that one react instance :

module.exports = {
resolve: {
    alias: {
      react: path.resolve('./node_modules/react')
    }
  },
 // other webpack settings
}
rettgerst commented 4 years ago

lerna can cause this problem, as I've learned (lerned?) today.

lerna users, if you are developing a component library, do not include react in your library's dependencies, put it in your peerDependencies and @types/react in your devDependencies.

jasondarwin commented 4 years ago

For anyone using lerna, you may come across this problem when running tests in one package, where the code references components in another package.

The problem that we experienced in this case was due to react being imported in the spec, and also being imported in a component in the component tree that was using Material UI's withStyles, which is implemented using hooks in Material UI.

It seems that react internally manages state in a ReactCurrentDispatcher.current variable, and this ends up getting set in one instance of react, but used in the other instance of react -- when it's empty, it throws the Invalid hook call ... message.

We were already using Craco to override Create React App's webpack config at build time:

  webpack: {
    alias: {
      react: path.resolve(__dirname, './node_modules/react'),
    },
  },

However, this webpack override is only used at build time -- when running the tests, the code isn't built, but instantiated from source -- so our solution was to make use of CracoAlias in our craco.config.js to specify the react path during the tests:

  plugins: [
    {
      plugin: CracoAlias,
      options: {
        source: 'options',
        baseUrl: './',
        aliases: {
          // We need to alias react to the one installed in the desktop/node_modules
          // in order to solve the error "hooks can only be called inside the body of a function component"
          // which is encountered during desktop jest unit tests,
          // described at https://github.com/facebook/react/issues/13991
          // This is caused by two different instances of react being loaded:
          // * the first at packages/desktop/node_modules (for HostSignUpDownloadComponent.spec.js)
          // * the second at packages/components/node_modules (for packages/components/Modal)
          react: './node_modules/react',
        },
      },
    },
  ],
Ksound22 commented 3 years ago

I get invalid Hook call problem only when i use material ui, with the number one possible reason being you might have mismatching versions of react and the renderer (such as React DOM). Its been happening every time for a month now and i have no clue on a solution. I've browsed through a lot on stackoverflow but no luck.

MeenakshiSundaram-MS commented 3 years ago

My library and the create-react-app are relative to each other and I am using npm link

Did you read this part?

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

It directly addresses the link workflow:

Screen Shot 2019-04-04 at 09 28 47

Thanks a lot for saving my ass <3

mlancaster-endpointclosing commented 3 years ago

For anyone else that end up here, I had the same issue after linking @ui-lib with projectA.

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://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

After arriving https://github.com/facebook/react/issues/15315#issuecomment-479802153, my first test to confirm the linking issue by removing the link yarn unlink @ui-lib in projectA and yarn install --force then copying the dist folder from @ui-lib into the projectA/node_modules/@ui-lib/dist and everything worked as expected.

So now instead of copying and pasting I can use something like https://github.com/wclr/yalc

adderly commented 3 years ago

I had this problem, and solved it with this: https://github.com/facebook/react/issues/13991#issuecomment-830308729

Not so sure on the why, but it was mainly a library using expo version of rn (react-native-web was the issue i think).

minsoo-web commented 3 years ago

Hello, everyone. I hope no one makes the same mistake as me and leave this comment.

import React, { useEffect } from "react";
import {useSelector} from "react-redux";

const myComponents = () => {
  useEffect(()=>{
    useSelctor(state => state.etc);
  },[]);

  return <></>;
}

export default myComponents;

By writing the code like this, I got into the same issue. In my case, the problem was not the version issue of react, but the incorrect way Hooks called.

solving like this

import React from "react";
import {useSelector} from "react-redux";

const myComponents = () => {
  const { etc2 } = useSelctor(state => state.etc);

  return <></>;
}

export default myComponents;

If you encounter the error code as above, I recommend you to check how to use Hooks again. 🙇

dipankar08 commented 3 years ago

just delete the node_module/react from your lib and try again.

jkalberer commented 2 years ago

While @Yolantele's solution works when you only have a single peer dependency, you would need to do this for every peer dependency to make this work.

const path = require('path');
const fs = require('fs');

// It would be best if you could just auto-resolve the folder names for the linked modules
const LINKED_FOLDER_NAMES = ['my-dep']
const alias = LINKED_FOLDER_NAMES
  .map(linkedDependency => {
    const rawdata = fs.readFileSync(`../${linkedDependency}/package.json`);
    const json = JSON.parse(rawdata);
    return Object.keys(json.peerDependencies || {}) || [];
  })
  .flat()
  .reduce((acc, dependency) => ({
    ...acc,
    [dependency]: path.resolve(`./node_modules/${dependency}`)
  }), {});

Config for webpack

webpack: {
  alias,
},

Config for gatsby

exports.onCreateWebpackConfig = ({ stage, actions }) => {
  actions.setWebpackConfig({
    resolve: { alias }
  });
};
hayesmaker commented 2 years ago

lerna can cause this problem, as I've learned (lerned?) today.

lerna users, if you are developing a component library, do not include react in your library's dependencies, put it in your peerDependencies and @types/react in your devDependencies. @types/react in your devDependencies.

do you know how to fix this if you're not using typescript?

namanSaxena500 commented 1 year ago

image

I am unable to get console of inside my useEffect but i am getting console outside my useEffect