开发React OTP组件时遭遇Invalid Hook Call错误求助
警告信息
Warning: 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. at OtpInput (http://localhost:3000/main.f021bb100c5d3bb39209.hot-update.js:22:76) at header at div at App
场景描述
开发一个计划发布到npm的React OTP组件,使用npm link在测试React应用中测试时出现上述警告。移除useState钩子仅渲染基础JSX时,组件可正常工作。
组件代码
import React, { FC, useState } from "react"; interface Props { numInputs: number; } const OtpInput: FC<Props> = ({ numInputs }): JSX.Element => { const [otp, setOtp] = useState(new Array(numInputs || 4).fill("")); return ( <div> {otp.map((_, index) => { return ( <React.Fragment key={index}> <input type="number" /> </React.Fragment> ); })} </div> ); }; export default OtpInput;
已尝试的操作
- 将react和react-dom从开发依赖移除,仅保留为peer依赖(版本均为18.2.0),但问题未解决。
依赖查询结果
执行npm list react得到:
├─┬ @testing-library/react@13.4.0 │ └── react@18.2.0 deduped ├─┬ react-dom@18.2.0 │ └── react@18.2.0 deduped ├─┬ react-otp-input-component@1.0.0 -> ./../react-otp-input-component │ ├─┬ react-dom@18.2.0 │ │ └── react@18.2.0 deduped │ └── react@18.2.0 ├─┬ react-scripts@5.0.1 │ └── react@18.2.0 deduped └── react@18.2.0
执行npm list react-dom得到:
├─┬ @testing-library/react@13.4.0 │ └── react-dom@18.2.0 deduped ├── react-dom@18.2.0 └─┬ react-otp-input-component@1.0.0 -> ./../react-otp-input-component └── react-dom@18.2.0
疑问
请问为何会出现这个错误?
问题原因
虽然npm list显示React版本完全一致,但通过npm link关联后,组件项目和测试应用实际上加载了两个独立的React实例——组件项目的node_modules目录中可能仍存在React(即便已设为peer依赖,若devDependencies未彻底移除,npm仍会安装本地副本)。React的钩子机制依赖单一全局实例来维护上下文,多个实例会导致钩子调用无法被正确识别,从而触发警告。
解决方法
清理组件的本地React依赖
打开组件项目的package.json,确保react和react-dom仅出现在peerDependencies中,从dependencies和devDependencies中彻底删除相关条目,然后执行npm install,确保组件本地不再安装React。让组件复用测试应用的React实例
切换到组件项目目录,执行以下命令,将组件的React依赖链接到测试应用的React副本:npm link ../测试应用的绝对路径/node_modules/react npm link ../测试应用的绝对路径/node_modules/react-dom这会强制组件使用测试应用的React实例,确保全局只有一个React上下文。
重启开发服务器
完成上述操作后,重启测试应用的开发服务器,确保新的依赖配置生效。
如果使用Create React App且无法直接修改Webpack配置,可借助react-app-rewired自定义配置,添加React别名强制使用同一实例:
// 项目根目录下的config-overrides.js const path = require('path'); module.exports = function override(config) { config.resolve.alias = { ...config.resolve.alias, 'react': path.resolve(__dirname, 'node_modules/react'), 'react-dom': path.resolve(__dirname, 'node_modules/react-dom') }; return config; };
内容的提问来源于stack exchange,提问作者Sandeep Gumaste

