导出调用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
相关产品推荐
相关产品推荐

