Next.js调用自定义Hook useUserSigner出现TypeError与无效Hook调用问题
错误原因
- 第一个
useUserSigner is not a function报错:
你的自定义Hook采用export default默认导出,最初使用import { useUserSigner }的命名导入语法无法获取到导出的函数,修改为默认导入语法后该问题解决。 - 第二个
Invalid hook call报错:
违反了React Hook的核心调用规则:Hook只能在函数组件的顶层作用域调用,不能在事件回调、异步函数、嵌套函数等非顶层位置调用。你将useUserSigner放在了点击事件触发的ConnectMetamask异步函数内执行,因此触发报错。
修复方案
不需要把Hook改成函数组件,只要调整Hook的调用位置即可:
- 将
useUserSigner的调用移到ConnectWallet组件的顶层作用域,Hook会自动监听传入的injectedProvider、localProvider参数变化,自动更新返回的userSigner值 - 新增
useEffect监听userSigner的变化,拿到有效值后执行后续业务逻辑 - 可选优化:自定义Hook中你使用
useMemo执行副作用逻辑不符合规范,建议替换为useEffect
修复后代码示例
src/pages/components/ConnectWallet.jsx
import { useState, useEffect } from "react"; import { ethers } from 'ethers' import ConnectMetamaskButton from '../components/ConnectMetamaskButton'; // 保持默认导入写法 import useUserSigner from "../../hooks/UserSigner"; import detectEthereumProvider from '@metamask/detect-provider'; export default function ConnectWallet(props) { const [Accounts, setAccounts] = useState("Connect Metamask"); const [injectedProvider, setInjectedProvider] = useState(); const ethersProvider = ethers.getDefaultProvider("ropsten"); // Hook调用移到组件顶层 const userSigner = useUserSigner(injectedProvider, ethersProvider); // 监听userSigner变化,拿到值后执行后续逻辑 useEffect(() => { if (userSigner) { console.log("userSigner = ", userSigner); // 此处补充拿到signer后的业务代码 } }, [userSigner]) async function ConnectMetamask() { try { const newAccounts = await ethereum.request({ method: 'eth_requestAccounts', }) setAccounts(newAccounts[0]); } catch (error) { console.error(error); } // 注意:你原代码中的getBalases未定义,请自行补充实现 // getBalases(); const provider = await detectEthereumProvider(); setInjectedProvider(new ethers.providers.Web3Provider(provider)); }; return ( <div> <header className="text-gray-100 bg-gray-900 body-font shadow w-full"> <div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center"> <nav className="flex lg:w-2/5 flex-wrap items-center text-base md:ml-auto"> <a className="mr-5 hover:text-gray-900 cursor-pointer border-b border-transparent hover:border-indigo-600"></a> </nav> <a className="flex order-first lg:order-none lg:w-1/5 title-font font-medium items-center lg:items-center lg:justify-center mb-4 md:mb-0"> <span className="ml-3 text-xl">L2 DEX HUB</span> </a> <div className="lg:w-2/5 inline-flex lg:justify-end ml-5 lg:ml-0"> <ConnectMetamaskButton Accounts={Accounts} onClick={() => { ConnectMetamask(); }}></ConnectMetamaskButton> </div> </div> </header> </div> ); };
可选优化:src/hooks/UserSigner.js
// 把useMemo替换为更适合执行副作用的useEffect import { useEffect, useState } from "react"; const useUserSigner = (injectedProvider, localProvider) => { const [signer, setSigner] = useState(); useEffect(() => { if (injectedProvider) { console.log("🦊 Using injected provider"); const injectedSigner = injectedProvider._isProvider ? injectedProvider.getSigner() : injectedProvider; setSigner(injectedSigner); } else if (!localProvider) setSigner(); else { if ( window.location.pathname && window.location.pathname.indexOf("/pk") >= 0 ) { const incomingPK = window.location.hash.replace("#", ""); let rawPK; if (incomingPK.length === 64 || incomingPK.length === 66) { console.log("🔑 Incoming Private Key..."); rawPK = incomingPK; window.history.pushState({}, "", "/"); const currentPrivateKey = window.localStorage.getItem("metaPrivateKey"); if (currentPrivateKey && currentPrivateKey !== rawPK) { window.localStorage.setItem( "metaPrivateKey_backup" + Date.now(), currentPrivateKey ); } window.localStorage.setItem("metaPrivateKey", rawPK); } } } }, [injectedProvider, localProvider]); return signer; }; export default useUserSigner;
内容的提问来源于stack exchange,提问作者user16697491
相关产品推荐
相关产品推荐

