将 MUI 组件添加到组件库会导致错误“只能在功能组件的主体内部调用挂钩”

Posted

技术标签:

【中文标题】将 MUI 组件添加到组件库会导致错误“只能在功能组件的主体内部调用挂钩”【英文标题】:Adding MUI Component to Component Library results in Error "Hooks can only be called inside of the body of a functional component" 【发布时间】:2021-08-22 00:55:00 【问题描述】:

我有一个导出组件的组件库

// Test.tsx
import React from 'react';
const Test = () => <p>Test</p>;
export default Test;

// TestB.tsx
import React from 'react';
import  Typography  from '@material-ui/core';

const TestB = () => <Typography>TestB</Typography>;

export default TestB

在我的另一个包中,我正在导入组件

import React from 'react';
import  Box  from '@components';
import  Test, TestB  from 'component-library';

const Example = (): JSX.Element | null => 
  return (
    <Box mb=3>
      <Test />
      <TestB />
    </Box>
  );
;

export default Example;

当我导入 Test 时,我得到了预期的 &lt;p&gt;Test&lt;/p&gt;,但是当我导入 TestB 时,我得到了以下错误:

react.development.js:1476 Uncaught 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.
    at resolveDispatcher (react.development.js:1476)
    at Object.useContext (react.development.js:1484)
    at useTheme (useTheme.js:4)
    at useStyles (makeStyles.js:222)
    at WithStyles (withStyles.js:55)
    at renderWithHooks (react-dom.development.js:14803)
    at updateForwardRef (react-dom.development.js:16816)
    at beginWork (react-dom.development.js:18645)
    at htmlUnknownElement.callCallback (react-dom.development.js:188)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:237)

    我已经确认reactreact-dom 都在16.9.0 版本上 -- 虽然这似乎无关紧要,因为它适用于Test,但不适用于TestB

    我不知道如何通过实现 TestB 来破坏 Rules of Hooks,因为它不使用任何钩子

    我已经尝试通过 package.json 删除除了一个版本之外的所有 react -- 这似乎并不相关,因为它适用于Test,但不适用于TestB

组件库 package.json 依赖项

  "dependencies": 
    "@material-ui/core": "4.11.4",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-is": "^17.0.2",
    "react-svg": "^13.0.6"
  ,
  "devDependencies": 
    "@babel/core": "^7.14.3",
    "@types/jest": "^24.0.24",
    "@typescript-eslint/eslint-plugin": "^4.25.0",
    "@typescript-eslint/parser": "^4.25.0",
    "babel-loader": "^8.2.2",
    "eslint": "^7.27.0",
    "eslint-config-airbnb": "^18.2.1",
    "eslint-plugin-import": "^2.23.3",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "eslint-plugin-react": "^7.23.2",
    "eslint-plugin-react-hooks": "^4.2.0",
    "jest": "^24.9.0",
    "rollup": "^1.27.13",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-peer-deps-external": "^2.2.0",
    "rollup-plugin-typescript2": "^0.25.3",
    "standard": "^14.3.1",
    "standard-prettier": "^1.0.1",
    "ts-jest": "^24.2.0",
    "typescript": "^3.7.3"
  ,

项目 package.json 依赖项

  "dependencies": 
    "@babel/core": "^7.1.6",
    "@babel/plugin-proposal-class-properties": "^7.1.0",
    "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/plugin-transform-flow-strip-types": "^7.1.6",
    "@babel/plugin-transform-modules-commonjs": "^7.4.3",
    "@babel/polyfill": "^7.6.0",
    "@babel/preset-env": "^7.9.0",
    "@babel/preset-react": "^7.0.0",
    "@babel/preset-typescript": "^7.9.0",
    "@date-io/moment": "1.x",
    "@emotion/core": "^10.0.10",
    "@material-ui/core": "^4.11.4",
    "@material-ui/icons": "^4.9.1",
    "@material-ui/lab": "^4.0.0-alpha.56",
    "@material-ui/pickers": "^3.2.10",
    "@stripe/react-stripe-js": "^1.1.2",
    "@stripe/stripe-js": "^1.4.0",
    "@styled-system/css": "^1.0.3",
    "@typeform/embed": "^0.5.12",
    "attr-accept": "^1.1.0",
    "autoprefixer": "^8.0.0",
    "awesome-typescript-loader": "^4.0.0",
    "axios": "^0.19.2",
    "babel-loader": "^8.0.4",
    "babel-plugin-dynamic-import-node": "^2.3.0",
    "babel-plugin-styled-components": "^1.10.0",
    "classnames": "^2.2.5",
    "compression-webpack-plugin": "^1.0.0",
    "core-js": "^3.2.1",
    "cropperjs": "^1.3.3",
    "css-hot-loader": "^1.3.6",
    "css-loader": "^0.28.7",
    "d3": "^6.2.0",
    "d3-selection": "^2.0.0",
    "date-fns": "^2.9.0",
    "dot-prop-immutable": "^1.4.0",
    "downshift": "^5.0.3",
    "emotion-theming": "^10.0.10",
    "empty": "^0.10.1",
    "enzyme": "^3.6.0",
    "enzyme-adapter-react-16": "^1.5.0",
    "enzyme-to-json": "^3.3.4",
    "eslint": "^7.3.1",
    "eslint-config-airbnb": "^16.1.0",
    "eslint-plugin-flowtype": "^2.46.1",
    "eslint-plugin-import": "^2.8.0",
    "eslint-plugin-jest": "^21.5.0",
    "eslint-plugin-jsx-a11y": "^6.0.2",
    "eslint-plugin-react": "^7.5.1",
    "extract-text-webpack-plugin": "^3.0.1",
    "faker": "^4.1.0",
    "fetch-mock": "^6.5.2",
    "file-loader": "^0.11.2",
    "file-saver": "^2.0.2",
    "final-form": "^4.20.1",
    "final-form-arrays": "^3.0.2",
    "final-form-calculate": "^1.3.1",
    "glob": "^7.1.2",
    "hellosign-embedded": "^2.8.0",
    "identity-obj-proxy": "^3.0.0",
    "isomorphic-fetch": "^2.2.1",
    "js-yaml": "^3.10.0",
    "jwt-decode": "^2.2.0",
    "lint-staged": "^10.2.11",
    "loaders.css": "^0.1.2",
    "lost": "^8.2.0",
    "components": "https://github.com/example/components#dev",
    "mock-geolocation": "^1.0.11",
    "moment": "2.24.0",
    "moment-locales-webpack-plugin": "^1.2.0",
    "moment-timezone": "^0.5.28",
    "node-sass": "^4.12.0",
    "notistack": "^0.9.11",
    "numeral": "^2.0.6",
    "path-complete-extname": "^1.0.0",
    "postcss-automath": "^1.0.1",
    "postcss-calc": "^7.0.2",
    "postcss-flexbugs-fixes": "^3.2.0",
    "postcss-font-magician": "^2.0.0",
    "postcss-import": "^10.0.0",
    "postcss-initial": "^2.0.0",
    "postcss-loader": "^2.0.6",
    "postcss-smart-import": "^0.7.5",
    "precss": "^2.0.0",
    "prettier-eslint": "^8.7.0",
    "prop-types": "^15.5.10",
    "qs": "^6.5.1",
    "ramda": "^0.27.1",
    "rc-time-picker": "^3.7.2",
    "react": "17.0.2",
    "react-addons-shallow-compare": "^15.6.0",
    "react-confetti": "^6.0.0",
    "react-content-loader": "^4.0.1",
    "react-cropper": "^1.0.1",
    "react-dates": "^21.8.0",
    "react-datetime": "^2.16.3",
    "react-dom": "17.0.2",
    "react-final-form": "^6.5.1",
    "react-final-form-arrays": "^3.1.2",
    "react-final-form-listeners": "^1.0.3",
    "react-hot-loader": "^4.6.3",
    "react-measure": "^2.0.2",
    "react-modal": "^3.8.1",
    "react-motion": "^0.5.1",
    "react-onclickoutside": "^6.7.0",
    "react-portal": "^4.0.0",
    "react-pose": "^4.0.8",
    "react-pose-text": "^3.1.0",
    "react-query": "^2.5.13",
    "react-redux": "^7.2.0",
    "react-resize-detector": "^6.6.0",
    "react-router": "^5.1.2",
    "react-router-dom": "^5.1.2",
    "react-select": "^1.0.0-rc.10",
    "react-svg-loader": "^1.1.1",
    "react-switch": "^2.0.0",
    "react-table": "^7.0.1",
    "react-tabs": "^2.2.2",
    "react-test-renderer": "^16.5.2",
    "react-text-mask": "^5.4.3",
    "react-textfit": "^1.1.0",
    "react-use": "^15.3.4",
    "react-virtualized-auto-sizer": "^1.0.2",
    "react-window": "^1.8.6",
    "react-window-infinite-loader": "^1.0.5",
    "react-youtube": "^7.5.0",
    "react_ujs": "^2.4.4",
    "recompose": "^0.26.0",
    "redux": "^4.0.1",
    "redux-actions": "^2.6.5",
    "redux-analytics": "^0.3.1",
    "redux-form": "^8.3.6",
    "redux-mock-store": "^1.4.0",
    "redux-observable": "^1.1.0",
    "redux-persist": "^5.6.12",
    "redux-responsive": "^4.3.8",
    "redux-thunk": "^2.2.0",
    "regenerator-runtime": "^0.11.0",
    "reselect": "^3.0.1",
    "resolve-url-loader": "^2.1.0",
    "rxjs": "^6.4.0",
    "sass-loader": "^6.0.6",
    "shortid": "^2.2.8",
    "sinon": "^6.3.5",
    "start-server-and-test": "^1.7.11",
    "style-loader": "^0.18.2",
    "styled-components": "^4.1.3",
    "styled-normalize": "^8.0.4",
    "styled-system": "^3.2.1",
    "svg-react-loader": "^0.4.5",
    "swr": "^0.2.0",
    "ts-jest": "^23.1.3",
    "typescript": "^3.8.3",
    "typings-for-css-modules-loader": "^1.7.0",
    "warnings-to-errors-webpack-plugin": "^2.0.1",
    "webpack": "^3.12.0",
    "webpack-bundle-analyzer": "^3.0.3",
    "webpack-dev-server": "^2.7.1",
    "webpack-manifest-plugin": "^1.3.1",
    "webpack-merge": "^4.1.0",
    "whatwg-fetch": "^3.0.0"
  ,
  "devDependencies": 
    "@svgr/cli": "^5.4.0",
    "@testing-library/cypress": "^6.0.0",
    "@testing-library/react": "^8.0.6",
    "@types/classnames": "^2.2.3",
    "@types/faker": "^4.1.4",
    "@types/fetch-mock": "^6.0.3",
    "@types/intercom-web": "^2.8.7",
    "@types/jest": "^25.2.3",
    "@types/jwt-decode": "^2.2.1",
    "@types/moment-timezone": "^0.5.4",
    "@types/qs": "^6.5.1",
    "@types/query-string": "^6.3.0",
    "@types/ramda": "^0.27.36",
    "@types/react": "^16.7.20",
    "@types/react-content-loader": "^3.1.4",
    "@types/react-dates": "^17.1.14",
    "@types/react-dom": "^16.0.11",
    "@types/react-loadable": "^5.4.2",
    "@types/react-measure": "^2.0.2",
    "@types/react-modal": "^3.8.2",
    "@types/react-motion": "^0.0.26",
    "@types/react-onclickoutside": "^6.0.3",
    "@types/react-redux": "^7.1.9",
    "@types/react-router-dom": "^5.1.3",
    "@types/react-select": "^1.2.7",
    "@types/react-table": "^7.0.13",
    "@types/react-test-renderer": "^16.0.2",
    "@types/recompose": "^0.26.1",
    "@types/redux-actions": "^2.6.0",
    "@types/redux-form": "^7.4.16",
    "@types/redux-mock-store": "^1.0.0",
    "@types/shortid": "^0.0.29",
    "@types/sinon": "^5.0.5",
    "@types/styled-components": "^4.1.18",
    "@types/styled-system": "^3.1.1",
    "@types/webpack-env": "^1.13.6",
    "@typescript-eslint/eslint-plugin": "^3.4.0",
    "@typescript-eslint/parser": "^3.4.0",
    "cypress": "6.1.0",
    "eslint-config-prettier": "^6.11.0",
    "eslint-plugin-prettier": "^3.1.4",
    "jest": "^26.0.1",
    "miragejs": "^0.1.35",
    "pre-commit": "^1.2.2",
    "prettier": "^2.0.5"
  ,

有什么想法吗?

【问题讨论】:

向我们展示您如何导入和使用TextB @Dominik 更新 能否在return语句前console.log(TestB)确保它是一个组件/函数? @JordanTheGenius $$typeof: Symbol(react.element), type: ..., key: null, ref: null, props: ..., ... $$typeof: Symbol( react.element) key: null props: children: "-Tested-" ref: null type: $$typeof: Symbol(react.forward_ref), propTypes: …, Naked: …, options: ...,渲染:ƒ,... _owner:null _store:已验证:false _self:null _source:null proto:Object 版本相关,Typography 内部使用了钩子,这就是为什么您在 TestB 中遇到错误的原因,请尝试在项目中设置与组件包中相同的 react 版本 【参考方案1】:

将大部分内容移至 devDependencies 并添加准备脚本似乎是我所缺少的。

我的 package.json 最终看起来像这样


  "version": "0.1.0",
  "main": "dist/index.js",
  "module": "build/index.es.js",
  "jsnext:main": "build/index.es.js",
  "files": ["dist", "build"],
  "scripts": 
    "prepare": "npm run build",
    "build": "rollup -c",
    "format": "prettier-standard --format",
    "test": "jest --coverage",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "lint": "eslint 'src/**/*.j,ts,j,tsx'",
  ,
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": ,
  "peerDependencies": 
    "@material-ui/core": "4.11.4",
    "react": "^16"
  ,
  "devDependencies": 
    "@babel/core": "^7.14.3",
    "@material-ui/core": "4.11.4",
    "@storybook/addon-actions": "^6.2.9",
    "@storybook/addon-essentials": "^6.2.9",
    "@storybook/addon-knobs": "^6.2.9",
    "@storybook/addon-links": "^6.2.9",
    "@storybook/react": "^6.2.9",
    "@types/jest": "^24.0.24",
    "@typescript-eslint/eslint-plugin": "^4.25.0",
    "@typescript-eslint/parser": "^4.25.0",
    "babel-loader": "^8.2.2",
    "eslint-config-airbnb": "^18.2.1",
    "eslint-plugin-import": "^2.23.3",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "eslint-plugin-react-hooks": "^4.2.0",
    "eslint-plugin-react": "^7.23.2",
    "eslint": "^7.27.0",
    "jest": "^24.9.0",
    "react-dom": "^17.0.2",
    "react-is": "^17.0.2",
    "react-svg": "^13.0.6",
    "react": "^17.0.2",
    "rollup-plugin-commonjs": "^10.1.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-peer-deps-external": "^2.2.0",
    "rollup-plugin-typescript2": "^0.25.3",
    "rollup": "^1.27.13",
    "standard-prettier": "^1.0.1",
    "standard": "^14.3.1",
    "storybook-addon-paddings": "^4.1.1",
    "storybook-dark-mode": "^1.0.8",
    "ts-jest": "^24.2.0",
    "typescript": "^3.7.3"
  ,
  "jest": 
    "preset": "ts-jest",
    "testEnvironment": "node"
  ,
  "standard": 
    "ignore": [
      "node_modules/",
      "build/"
    ]
  


并按要求提供 rollup.config

import typescript from 'rollup-plugin-typescript2'
import commonjs from 'rollup-plugin-commonjs'
import external from 'rollup-plugin-peer-deps-external'
import resolve from 'rollup-plugin-node-resolve'

import pkg from './package.json'

export default 
  input: 'src/index.ts',
  output: [
    
      file: pkg.main,
      format: 'cjs',
      exports: 'named',
      sourcemap: true
    ,
    
      file: pkg.module,
      format: 'es',
      exports: 'named',
      sourcemap: true
    
  ],
  external: ["react", "@material-ui/core", "react-is"],
  plugins: [
    external(),
    resolve(),
    typescript(
      rollupCommonJSResolveHack: true,
      exclude: [
        '**/__tests__/**',
        '**/*.stories.*'
      ],
      clean: true
    ),
    commonjs(
      include: ['node_modules/**'],
      namedExports: 
    )
  ]

【讨论】:

您需要 material-ui 并作为 peerDependencies 做出反应的任何原因?那是错误的试验吗? 你能分享你的汇总配置吗?你对@rollup/plugin-node-resolverollup-plugin-peer-deps-external 做了什么吗? 我的理解是对等依赖调用它可以期望主机包已经拥有该包。这是一篇文章nodejs.org/es/blog/npm/peer-dependencies

以上是关于将 MUI 组件添加到组件库会导致错误“只能在功能组件的主体内部调用挂钩”的主要内容,如果未能解决你的问题,请参考以下文章

MUI - 如何将组件对齐到中心/右侧?

MUI 组件中的媒体查询

样式化的 MUI 无法在 typescript 中将组件道具传递给 Typography

如何在 MUI 中单击卡片时添加波纹效果

MUI createTheme 未正确将主题传递给 MUI 组件

如何向 React/MUI 自动完成组件添加唯一键?