Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hooks + multiple instances of React #13991

Open
brunolemos opened this issue Oct 27, 2018 · 368 comments
Open

Hooks + multiple instances of React #13991

brunolemos opened this issue Oct 27, 2018 · 368 comments

Comments

@brunolemos
Copy link

@brunolemos brunolemos commented Oct 27, 2018

To people coming from search: please read this page first. It contains most common possible fixes!

Do you want to request a feature or report a bug?

Enhancement

What is the current behavior?

I had multiple instances of React by mistake.

When trying to use hooks, got this error:
hooks can only be called inside the body of a function component

Which is not correct since I was using function components. Took me a while to find the real cause of the issue.

What is the expected behavior?

Show the correct error message. Maybe detect that the app has multiple instances of React and say that it may be the reason of bugs.

@brunolemos brunolemos changed the title Wrong hooks error message when app has multiple instances of React Hooks + multiple instances of React Oct 27, 2018
@philipp-spiess
Copy link
Member

@philipp-spiess philipp-spiess commented Oct 27, 2018

So just for clarification: You were importing a hook (say useState) from a different react module than the one used to render the component?

I agree that this is confusing. I'm not sure though if we have a way of knowing if any other React module is rendering. AFAIK we try to run React in isolation as much as possible so that multiple React instances can work in the same global context without issues.

Otherwise we could probably update the error message and mention this case as well if it's not too confusing.

@brunolemos
Copy link
Author

@brunolemos brunolemos commented Oct 27, 2018

Yes, I compared React1 === React2 and it was false (React1 being from index.js and React2 being from the file using the hook). When this happens, hooks fail with the generic error message above.

This issue is to raise awareness of this case and maybe improve the error message in some way to help people that face this. It's probably very edge though.

@GabrielBB
Copy link

@GabrielBB GabrielBB commented Nov 1, 2018

Yup, i tried to npm link a package i'm creating. It throws that same error since the other package is also using hooks but with its own React. I had to publish my package to NPM and then import it directly from NPM. That way the error was gone, but i hope this is fixed since publishing a package without testing it is bad, obviously

@mpeyper
Copy link

@mpeyper mpeyper commented Nov 2, 2018

Lerna monorepos suffer from this as well when a custom hook is defined in one package and used by another as the symlinked dependencies use their own copy of react.

I have a (hacky) workaround at the moment using npm-link-shared and a prestart npm script to essentially replace the one package's react dependency with a symlink to the other's, so they use the same instance.

"prestart": "npm-link-shared ./node_modules/<other package>/node_modules . react"

@apieceofbart
Copy link

@apieceofbart apieceofbart commented Nov 3, 2018

I had the same issue and I resolved it by adding:

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

to resolve property in webpack config of my main app.

It's was obviously my mistake of using two copies of React but I agree that it would be great if the error message was better. I think this is maybe similar to: #2402

@GabrielBB
Copy link

@GabrielBB GabrielBB commented Nov 5, 2018

@mpeyper It works. Thanks

@jimbo
Copy link

@jimbo jimbo commented Nov 18, 2018

@apieceofbart That worked for me. Thanks for the suggestion. 👍

@jbandi
Copy link

@jbandi jbandi commented Dec 3, 2018

As I understand this problem arises when there are multiple copies of React in the same bundle.

Is this also a problem if two separate bundles with their own copies of React are bootstrapping their own React applications on separate dom elements, like described here: https://medium.jonasbandi.net/hosting-multiple-react-applications-on-the-same-document-c887df1a1fcd

I think the latter is a common "integration" pattern used for instance in the single-spa meta-framework (https://github.com/CanopyTax/single-spa).

@vpicone
Copy link

@vpicone vpicone commented Dec 10, 2018

I'm also having this issue even with the exact same react versions, developing hooks to be published on their own is broken when using npm-link. Getting the same unhelpful hooks can only be called inside the body of a function component message. @apieceofbart's alias solution solved this for me. Thanks so much!

@dotlouis
Copy link

@dotlouis dotlouis commented Dec 11, 2018

Same issue here when I npm link a package to my main application. I could not get babel-plugin-module-resolver working.
It says:
Could not find module './node_module/react'
This is annoying because it prevents me from testing my component locally before publishing it.

@doasync
Copy link

@doasync doasync commented Dec 22, 2018

I fixed my issue by removing the caret in "react": "^16.7.0-alpha.2"
Here is the full comment: #14454 (comment)

@pelotom
Copy link

@pelotom pelotom commented Dec 22, 2018

I'm using Yarn, and fixed this by forcing resolution in my package.json:

  "resolutions": {
    "**/react": "16.7.0-alpha.2",
    "**/react-dom": "16.7.0-alpha.2"
  },

@leecade
Copy link

@leecade leecade commented Dec 24, 2018

Same here!!

@zbuttram
Copy link

@zbuttram zbuttram commented Feb 5, 2019

Just wanted to leave a note here for anyone who might have had this problem in the same manner I did.

We're running React and Rails with the react-rails gem and rendering components directly into Rails views. I was receiving this error every time a new version of the app was pushed, because Turbolinks was grabbing the new JS bundle out of the <head> which loaded up an extra instance of React. Solution was to have Turbolinks do a full page reload when it detects the bundle has changed: https://github.com/turbolinks/turbolinks#reloading-when-assets-change

This appears to have solved it for us.

@taylorham
Copy link

@taylorham taylorham commented Feb 6, 2019

I'm very excited to finally put Hooks into production, and we all owe a huge thank you to everyone who made it possible. They're a ton of fun to work with and have made my code shorter and more declarative.

Just as a heads up, this issue is still relevant in the released version with the same unhelpful error message of "Hooks can only be called inside the body of a function component."

Is this something that can be fixed? I imagine it might become more and more prevalent as more devs start to implement the new features, and a clearer error message would go a long way in lieu of an outright "fix".

Thanks again for all the hard work and congrats on the release! It's really an amazing set of features.

Edit: Should have looked closer at the open PRs, just found #14690 that addresses this. Thanks @threepointone!

@waweber
Copy link

@waweber waweber commented Jul 1, 2021

I've been struggling with this issue as well, but I found the solution for my use case.

I have a monorepo, containing a shared React component library, and an application that uses the component library via a file:../mylib/lib dependency. The component library does not use a bundler, but the application uses webpack.

A simplified view of the repository looks like this:

/mylib
    lib/ (tsc output)
        index.js
        package.json (copied from parent dir on build)
            ^ has react as a peer dependency, and a dev dependency
    node_modules/
        ^ contains react, installed as a dev dependency
/myapp
    index.jsx
    package.json
        ^ has a dependecy on file:../mylib/lib
    node_modules/
        ^ contains react, as a normal dependency
        mylib -> ../../mylib/lib (npm symlinks it)
    webpack.config.js

In this setup, I get an invalid hook call when using a component from mylib that uses a state hook. This is why:

  1. /myapp/index.jsx imports a component from mylib
  2. Webpack attempts to resolve mylib, by recursively walking up the directory tree looking for node_modules
  3. Webpack finds /myapp/node_modules and finds mylib (as a symlink) within it
  4. Webpack resolves the symlink and treats the path of mylib as /mylib/lib/index.jsx
  5. /mylib/lib/index.jsx imports react.
  6. Webpack attempts to resolve react, using the same method as before. Because the path for the mylib module was resolved to /mylib/lib/index.jsx, Webpack finds /mylib/node_modules and looks for React there
  7. A second React is imported

The solution that worked for me was to set resolve.symlinks: false in my Webpack config. Thus, the path for mylib remains /myapp/node_modules/mylib and the other node_modules with the dev dependencies isn't discovered.

You could also set resolve.modules: to strictly only the current directory's node_modules instead of trying to resolve wherever it finds one.

@TalalYousif
Copy link

@TalalYousif TalalYousif commented Jul 9, 2021

I've spent more than an hour on this issue and no comment here solved my issue until I came across this: https://stackoverflow.com/questions/62387392/react-native-invalid-hook-call-for-flat-list-render-item

Basically I was using a hook inside of a component that is used to render a flat list item

@shouravrahman
Copy link

@shouravrahman shouravrahman commented Jul 25, 2021

I cloned a repo and installed all dependencies directly from terminal and everything worked except for useHistory and useDispatch,it throwed the invalid hook call error.however I re-installed react-router-dom and react-redux directly from VS code's terminal and now it seems to be working fine.

@vasilev-alex
Copy link

@vasilev-alex vasilev-alex commented Jul 26, 2021

I tried all of the suggested solutions but none worked for me. I've created a simple library and build using Webpack https://github.com/vasilev-alex/webpack-example

As soon, as I import a React hook in my library and use it in my App I see the following 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.

I link my library (webpack-example) using the app's package.json

"dependencies": {
  "webpack-example": "link:../../webpack-example"
}

I ensure that I have only one copy of React by tweaking the react-scripts' webpack.config.js (node_modules/react-scripts/config/webpack.config.js)

    alias: {
      // Support React Native Web
      // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
      'react-native': 'react-native-web',
      // Allows for better profiling with ReactDevTools
      ...(isEnvProductionProfile && {
        'react-dom$': 'react-dom/profiling',
        'scheduler/tracing': 'scheduler/tracing-profiling',
      }),
      ...(modules.webpackAliases || {}),
// here
      react: path.resolve(__dirname, '../../react')
    },

P.S. if I bundle my lib using Rollup it works just fine.

@jonaldinger
Copy link

@jonaldinger jonaldinger commented Jul 26, 2021

My issue wound up being an old node_modules directory installed locally. We had previously moved our node_modules dir from a nested directory into the project root. So fresh copies of the repo worked fine, but the hooks error was present for any devs who hadn't manually deleted the original nested node_modules directory locally - this was causing two versions of React to be resolved, but this wasn't made visible by npm ls react. Deleting the outdated node_modules directory cleared it right up.

@JeffreyZhao
Copy link

@JeffreyZhao JeffreyZhao commented Aug 10, 2021

Want to leave some information regarding how I workaround this issue occurred to me in my environment. I've developing React application using create-react-app on Windows, which uses a case-insensitive file system, and that's exactly the reason that I got two React instances.

I've tried the window.React1 === window.React2 which returns false although I've very sure I have only one copy of React in my root node_modules folder in my monorepo. So I added a debugger statement in node_modules/react/index.js and noticed it really has been loaded twice, from the same path, but with two different "tags" (tags below are for description purpose only).

  1. First load: ../../node_module/react/index.js?1234
  2. First load: ../../node_module/react/index.js?abcd

After working through webpack, added console.log everywhere for hours, I've finally realized that the "tags" are hashes generated by the full path of that file in node_modules/webpack/lib/NamedModulesPlugin.js. The reason it's having two different hashes is because:

  1. The first hash is generated from c:\my-project\node_modules\react\index.js
  2. The second hash is generated from C:\my-project\node_modules\react\index.js

Notice the difference between C: and c:? So I apply the workaround by adding a postinstall script to patch the file node_modules/webpack/lib/NamedModulesPlugin.js.

Original:

"use strict";

const createHash = require("./util/createHash");
const RequestShortener = require("./RequestShortener");

const getHash = str => {
	const hash = createHash("md4");
	hash.update(str);
	const digest = /** @type {string} */ (hash.digest("hex"));
	return digest.substr(0, 4);
};

Patched:

"use strict";

// new helper function to convert the root driver to upper case when FIX_MODULE_HASH is set
function fixModulePath(str) {
  if (!process.env.FIX_MODULE_HASH) return str;
  return str.replace(/[a-z]:/g, m => m.toUpperCase())
}

const createHash = require("./util/createHash");
const RequestShortener = require("./RequestShortener");

const getHash = str => {
	const hash = createHash("md4");
	hash.update(fixModulePath(str)); // call fixModulePath
	const digest = /** @type {string} */ (hash.digest("hex"));
	return digest.substr(0, 4);
};

Hope it helps.

@TimNZ
Copy link

@TimNZ TimNZ commented Aug 14, 2021

@waweber

Thanks for mentioning webpack resolve.modules, that helped me sort this cleanly.
Hopefully this helps some other people in similar scenario.

ProjectA\source\packages\nextjsapp
ProjectB\source\packages\library

ProjectA\source\packages\nextjsapp: yarn link library

Both projects are using yarn workspaces/lerna rooted at the 'source' dir level.

Overriding resolve.modules is the tidiest fix for my scenario where using yarn/npm link to another library that is in a different directory tree and using a different install of React and other components i.e. different node_modules resolution paths.

For nextjsapp webpack config, I put traditional node_modules resolution at the end 'node_modules', prioritising node_modules dirs in ProjectA

if (process.env.NODE_ENV == 'development') {
  config.resolve.modules = [path.resolve(__dirname, 'src'),path.resolve(__dirname, 'node_modules'),path.resolve(__dirname, '../../node_modules'),'node_modules']
}

Now I don't have to explicitly set resolve.alias for specific modules e.g. React,
and I don't have to perform any other package.json or symlink magic in either Project repo

@mmilet02
Copy link

@mmilet02 mmilet02 commented Aug 28, 2021

Hi. I need help with my project, I am relatively new to React JS so sorry for tha lack of knowledge :). So I have small project, folder structure is:

rootFolder
| node_mdules
| public
| src
| package.json

So I found out that when I added this onSubmit function in AddTask.js file I get 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: ...

And I found out that I have Duplicate React and i dont't know why and where is it comming from. These are some of my files.

// ------ index.js ------
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
<React.StrictMode>

</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();

// ------ App.js ------
import { useState } from "react";
import Header from "./components/Header";
import Tasks from "./components/Tasks";
import AddTask from "./components/AddTask";

const App = () => {
const [tasks, setTasks] = useState([ ]);

// Add task
const addTask = (e) => {
console.log(e);
};
// Delete task
const deleteTask = (id) => {
setTasks(tasks.filter((task) => task.id !== id));
};
// Toggle Reminder
const toggleReminder = (id) => {
setTasks(
tasks.map((task) =>
task.id === id ? { ...task, reminder: !task.reminder } : task
)
);
};
return (




{tasks.length > 0 ? (

) : (
"No Tasks To Show"
)}

);
};

export default App;

//AddTask.js
import { useState } from "react";

const AddTask = ({ addTask }) => {
const [text, setText] = useState("");
const [day, setDay] = useState("");
const [reminder, setReminder] = useState(false);

const onSubmit = (e) => {
e.preventDefault();

if (!text) {
  alert("Please add a task");
  return;
}

setText("");
setDay("");
setReminder(false);

AddTask({ text, day, reminder });

};

return (



Task
<input
type="text"
placeholder="Add Task"
value={text}
onChange={(e) => setText(e.target.value)}
/>


Day & Time
<input
type="text"
placeholder="Add Day & Time"
value={day}
onChange={(e) => setDay(e.target.value)}
/>


Set Reminder
<input
type="checkbox"
checked={reminder}
value={reminder}
onChange={(e) => setReminder(e.currentTarget.checked)}
/>

  <input type="submit" value="Save Task" className="btn btn-block" />
</form>

);
};

export default AddTask;

//package.json

{
"name": "react-task-tracker",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-icons": "^4.2.0",
"react-scripts": "^4.0.3",
"web-vitals": "^1.1.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Sorry for long post, didn't know how to add text box :)

@TimNZ
Copy link

@TimNZ TimNZ commented Aug 28, 2021

You are calling the component function AddTask from within the onSubmit handler inside AddTask.
This is definitely not what you are intending.

@mmilet02
Copy link

@mmilet02 mmilet02 commented Aug 28, 2021

You are calling the component function AddTask from within the onSubmit handler inside AddTask.
This is definitely not what you are intending.

Wow, @TimNZ thank you a lot :D I was losing my mind for two day because of typo :(

@OfirD1
Copy link

@OfirD1 OfirD1 commented Aug 30, 2021

Using npm-link-shared as suggested by @mpeyper didn't work for me. For some reason, it just deletes the symlink to <other_package> from ./node_modules, and doesn't make a symlink to react in ./node_modules.

Also, using alias in webpack.config.js, as several have suggested, wasn't suitable in my case, since I just don't have one.

What did work was what explained in this StackOverflow answer, which is using craco. I'll just quote it here:

After following the installation, add craco.config.js to your root folder with the desired configuration.

My example:

// craco.config.js
const path = require(`path`);
const alias = require(`./src/config/aliases`);

const SRC = `./src`;
const aliases = alias(SRC);

const resolvedAliases = Object.fromEntries(
 Object.entries(aliases).map(([key, value]) => [key, path.resolve(__dirname, value)]),
);

module.exports = {
 webpack: {
   alias: resolvedAliases,
 },
};

Where aliases.js (./src/config/aliases) exports a helper function:

const aliases = (prefix = `src`) => ({
 '@atoms': `${prefix}/components/atoms`,
 [...]
 '@storybookHelpers': `../.storybook/helpers`,
});

module.exports = aliases;

@foolish-boy
Copy link

@foolish-boy foolish-boy commented Aug 31, 2021

My React-dom is 16.14.0 and has no copy react, and my code is using class component but import an component writing by hooks? I don't know why got this error! any one help~

@Yrobot
Copy link

@Yrobot Yrobot commented Sep 7, 2021

try link react from you app project:

cd component 
npm link ../app/node_modules/react

then restart your app

@PABourdais
Copy link

@PABourdais PABourdais commented Sep 8, 2021

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.

Using this command from react-native documentation in order to link react version from my app and my local lib did the job 👍

@Shikamaru1995
Copy link

@Shikamaru1995 Shikamaru1995 commented Sep 9, 2021

Any insight on why there are multiple react instances? I've set react and react-dom as peerdependencies in the imported library but still need to set alias to avoid this error.

@Dimuthu-10
Copy link

@Dimuthu-10 Dimuthu-10 commented Sep 10, 2021

import { useState } from 'react';
import "./Searchbar.css"
import SearchIcon from '@material-ui/icons/Search';
import MicIcon from '@material-ui/icons/Mic';
import { Button } from '@material-ui/core';
import { useHistory } from "react-router-dom";

function Searchbar() {
    const history = useHistory();
    const[input, setInput] = useState('');
   

    const search = (e) =>{
        e.preventDefault();

        // when the click enter button 
        
        history.push("/search")
    }

    return (
        <form className="search">
            <div className="search_box">
                <SearchIcon className="search_icon" />
                <input value={input} onChange={e => setInput(e.target.value)}></input>
                <MicIcon />
            </div>

            <div className="search_cards">
               <Button type='submit' onClick={search} variant="outlined">Google Search</Button>
               <Button variant="outlined">I'm Feeling Lucky</Button>
            </div>
        </form>
    )
}

export default Searchbar; 

this is the my code. i have tried to use useHistory hook to redirect to the searchpage when i have clicked that search button.
but after adding Use History hook it displays message like

Invalid hook call. Hooks can only be called inside of the body of a function component.

is it wrong that the way i have called useHistory hook???

@yedhukrishnagirish
Copy link

@yedhukrishnagirish yedhukrishnagirish commented Sep 17, 2021

function App (){
const counter = useSelector(state => state.counter);
const log = useSelector(state => state.log);
return (


Hello world {counter} {log}

);
}

I'm also facing same issue, what is wrong in it? but using redux dev tools I can see the changes as well

shinxi added a commit to shinxi/create-react-app that referenced this issue Sep 25, 2021
* Add webpack alias support for fixing npm link issue when using react hooks.
* See these links for more detail.
** facebook/react#13991
** facebook#2188
** facebook#3554
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet