You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React组件库出现Cannot read properties of null (reading 'useState')错误的解决

问题

我正在开发一个信用卡表单的React组件库,相关代码及配置如下:

CreditCardForm.tsx

import React, {useState} from "react";

interface CreditCardFormProps {
  testState: any;
  name: string;
  onChangeName: () => void;
  cardNumber: string;
  onChangeCardNumber: () => void;
  expirationDate: string;
  onChangeExpirationDate: () => void;
  cvv: string;
  onChangeCvv: () => void;
  onSubmit: () => void;
}


const CreditCardForm = (props: CreditCardFormProps) => {
  const [testState, setTestState] = useState<any>("hello")
  return (
    <form onSubmit={props.onSubmit}>
      <label htmlFor="name">Name on Card</label>
      <input
        type="text"
        id="name"
        name="name"
        value={props.name}
        onChange={props.onChangeName}
        required
        pattern="^[A-Za-z\s]*$"
        title="Please input your name correctly"
      />
      <p>test: {testState}</p>

      <label htmlFor="cardNumber">Card Number</label>
      <input
        type="text"
        id="cardNumber"
        name="cardNumber"
        value={props.cardNumber}
        onChange={props.onChangeCardNumber}
        required
        minLength={16}
        maxLength={16}
        pattern="^[0-9]*$"
        title="Your Card Number should contain only numbers."
      />
        <label htmlFor="expirationDate">Expiration Date</label>
        <input
          type="number"
          id="expirationDate"
          name="expirationDate"
          value={props.expirationDate}
          onChange={props.onChangeExpirationDate}
          required
          title="Please input your Expiration Date correctly"

        />

      <label htmlFor="cvv">CVV</label>
      <input
        type="text"
        id="cvv"
        name="cvv"
        value={props.cvv}
        onChange={props.onChangeCvv}
        required
        minLength={3}
        maxLength={3}
        title="Your CVV should contain only numbers."

      />

      <button type="submit">Submit</button>
    </form>
  );
};

export default CreditCardForm;

package.json

{
  "name": "@packagename",
  "version": "0.1.24",
  "description": "A react component library",
  "scripts": {
    "rollup": "rollup -c"
  },
  "author": " Name ",
  "license": "ISC",
  "peerDependencies": {
    "react-dom": "^18.2.0",
    "react": "^18.0.2"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^24.1.0",
    "@rollup/plugin-node-resolve": "^15.0.2",
    "@rollup/plugin-typescript": "^11.1.0",
    "@types/react": "^18.2.0",
    "rollup": "^3.21.0",
    "rollup-plugin-dts": "^5.3.0",
    "source-map-explorer": "^2.5.3",
    "tslib": "^2.5.0",
    "typescript": "^5.0.4"
  },
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "files": [
    "dist"
  ],
  "types": "dist/index.d.ts",
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/@username"
  },
  "dependencies": {
    "react": "^18.0.2",
    "react-dom": "^18.2.0"

  }
}

rollup.config.mjs

import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import packageJson from "../package.json" assert { type: "json" }; //we import package.json so when we use commonjs modules

export default [
  //first configuration object
  {
    input: "src/index.ts", //entry point for this part of our library (it exports all of our components so that the user can import the library)
    output: [
      {
        file: packageJson.main,
        format: "cjs",
        sourcemap: true,
      }, //commonjs modules, which will use the main field of packageJson module
      {
        file: packageJson.module,
        format: "esm",
        sourcemap: true,
      }, //for the es6 modules
    ],
    plugins: [
      resolve(),
      commonjs(),
      typescript({ tsconfig: "../tsconfig.json" }),
    ], //node resolve plugin, and the other plugins (typescript plugin needs the specific directory)
  },

  //second configuration object
  {
    input: "dist/esm/types/index.d.ts",
    output: [{ file: "dist/index.d.ts", format: "esm" }],
    plugins: [dts()],
  },
];

发布该库并在测试React项目中安装运行后,出现如下错误:

Compiled with problems:
×
ERROR
Cannot read properties of null (reading 'useState')
TypeError: Cannot read properties of null (reading 'useState')
    at Object.useState (http://localhost:3000/static/js/bundle.js:2135:29)
    at CreditCardForm (http://localhost:3000/static/js/bundle.js:3112:50)
    at renderWithHooks (http://localhost:3000/static/js/bundle.js:24102:22)
    at mountIndeterminateComponent (http://localhost:3000/static/js/bundle.js:27388:17)
    at beginWork (http://localhost:3000/static/js/bundle.js:28684:20)
    at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:13694:18)
    at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:13738:20)
    at invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:13795:35)
    at beginWork$1 (http://localhost:3000/static/js/bundle.js:33669:11)
    at performUnitOfWork (http://localhost:3000/static/js/bundle.js:32916:16)

原因分析

这个错误核心是组件库打包时把React和ReactDOM一起打包进了产物,导致测试项目中同时存在两份React实例:一份是项目自身依赖的React,另一份是组件库打包带进来的React。当组件调用useState时,实际引用的是组件库打包的那份未与项目React上下文绑定的实例,最终导致React对象为null,无法读取useState方法。

具体诱因:

  1. package.json中把React和ReactDOM放在了dependencies中,Rollup默认会将这类依赖打包进最终产物。
  2. 未配置Rollup的external选项,没有明确告知Rollup不要打包React这类peer依赖。

解决方法

步骤1:调整package.json依赖配置

把dependencies中的React和ReactDOM删除,保留peerDependencies中的配置即可,确保组件库不会安装自己的React副本,而是直接使用宿主项目的React:

修改后的依赖部分:

"peerDependencies": {
  "react-dom": "^18.2.0",
  "react": "^18.0.2"
},
"devDependencies": {
  "@rollup/plugin-commonjs": "^24.1.0",
  "@rollup/plugin-node-resolve": "^15.0.2",
  "@rollup/plugin-typescript": "^11.1.0",
  "@types/react": "^18.2.0",
  "rollup": "^3.21.0",
  "rollup-plugin-dts": "^5.3.0",
  "source-map-explorer": "^2.5.3",
  "tslib": "^2.5.0",
  "typescript": "^5.0.4"
}

步骤2:配置Rollup的external选项

在Rollup配置的第一个打包对象中添加external字段,指定React和ReactDOM为外部依赖,禁止Rollup将它们打包进产物:

修改后的rollup.config.mjs第一部分:

{
  input: "src/index.ts",
  output: [
    {
      file: packageJson.main,
      format: "cjs",
      sourcemap: true,
    },
    {
      file: packageJson.module,
      format: "esm",
      sourcemap: true,
    },
  ],
  external: ["react", "react-dom"], // 添加此配置
  plugins: [
    resolve(),
    commonjs(),
    typescript({ tsconfig: "../tsconfig.json" }),
  ],
},

步骤3:重新打包发布

执行npm run rollup重新打包组件库,然后重新发布到私有仓库。

步骤4:更新测试项目的组件库

在测试项目中重新安装组件库,确保使用修改后的版本,此时组件会正确复用项目自身的React实例,错误即可解决。

内容的提问来源于stack exchange,提问作者bytebunny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:27:35