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

导出调用useAtom的独立函数触发React Hook错误的解决方法

解决React中Jotai Hook在普通函数调用的错误

问题根源

你遇到的"React hook is called in a function"错误,是因为React Hook(包括Jotai的useAtom)只能在React组件的顶层,或者自定义Hook的顶层调用,不能在普通工具函数里使用——这是React Hook的强制规则,目的是保证Hook的调用顺序稳定。

你的setMetamaskWallet是普通函数,里面直接调用了useAtom,违反了这个规则,所以触发报错。

两种可行解决方案

方案1:封装成自定义Hook(推荐,符合React生态)

把你的逻辑改成自定义Hook,这样既可以复用逻辑,又符合Hook的调用规则。

// hooks/useMetamaskWallet.ts
import { useAtom } from 'jotai';
import { web3Atom, loadingAtom, walletAtom } from '../path/to/your/atoms';
import Web3 from 'web3';

export const useMetamaskWallet = () => {
  const [, setWeb3] = useAtom(web3Atom);
  const [, setLoading] = useAtom(loadingAtom);
  const [wallet, setWallet] = useAtom(walletAtom);

  const connect = async () => {
    // 先检查MetaMask是否存在
    if (!window.ethereum) {
      throw new Error('MetaMask未安装,请先安装浏览器扩展');
    }

    setWeb3(new Web3(window.ethereum));

    if (!wallet) {
      setLoading(true);
      try {
        const accounts = await window.ethereum.request({
          method: 'eth_requestAccounts'
        });
        setWallet(accounts[0]);
      } catch (err) {
        console.error('连接钱包失败:', err);
        // 这里可以添加错误提示逻辑,比如弹窗告知用户
      } finally {
        setLoading(false);
      }
    }
  };

  return { connect };
};

组件中使用:

// 比如ConnectWalletButton组件
import { useMetamaskWallet } from './hooks/useMetamaskWallet';

export const ConnectWalletButton = () => {
  const { connect } = useMetamaskWallet();

  const handleClick = async () => {
    try {
      await connect();
    } catch (err) {
      alert(err.message);
    }
  };

  return <button onClick={handleClick}>连接MetaMask</button>;
};

方案2:直接操作Jotai原子(类Angular服务模式)

如果你更习惯Angular服务的纯工具函数模式,可以直接用Jotai提供的get/set函数操作原子,不需要依赖React Hook,这样函数可以放在独立文件里直接调用。

// utils/metamaskService.ts
import { get, set } from 'jotai';
import { web3Atom, loadingAtom, walletAtom } from '../path/to/your/atoms';
import Web3 from 'web3';

export const connectMetamaskWallet = async () => {
  if (!window.ethereum) {
    throw new Error('MetaMask未安装,请先安装浏览器扩展');
  }

  // 直接更新web3原子
  set(web3Atom, new Web3(window.ethereum));

  const currentWallet = get(walletAtom);
  if (!currentWallet) {
    set(loadingAtom, true);
    try {
      const accounts = await window.ethereum.request({
        method: 'eth_requestAccounts'
      });
      set(walletAtom, accounts[0]);
    } catch (err) {
      console.error('连接钱包失败:', err);
    } finally {
      set(loadingAtom, false);
    }
  }
};

组件中使用:

// 比如ConnectWalletButton组件
import { connectMetamaskWallet } from './utils/metamaskService';

export const ConnectWalletButton = () => {
  const handleClick = async () => {
    try {
      await connectMetamaskWallet();
    } catch (err) {
      alert(err.message);
    }
  };

  return <button onClick={handleClick}>连接MetaMask</button>;
};

方案选择建议

  • 如果你的逻辑需要和组件的其他状态、Hook(比如useEffect、useState)联动,选自定义Hook方案,更贴合React的开发模式。
  • 如果只是单纯的全局状态更新,不需要依赖组件上下文,选直接操作原子方案,更接近你熟悉的Angular服务模式。

内容的提问来源于stack exchange,提问作者Wesley van S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 13:01:54