使用Solana集成时出现React Hooks调用错误求助
问题排查与解决:React Hook调用错误(Solana Dapp Scaffold)
错误信息
"Account gathering failed! 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..."
相关代码
ABI加载代码
let PROGRAM_ABI; // Define PROGRAM_ABI variable // Fetch the JSON file using fetch API fetch('../deployment_data/xxx.json') .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); // Parse the JSON response }) .then(data => { // Assign the parsed JSON data to PROGRAM_ABI PROGRAM_ABI = data; console.log('PROGRAM_ABI:', PROGRAM_ABI); }) .catch(error => { console.error('Error fetching JSON:', error); });
组件代码
export const GetContractAccounts: FC = () => { const { connection } = useConnection(); const { publicKey } = useWallet(); const { getUserSOLBalance } = useUserSOLBalanceStore(); const onClick = useCallback(async () => { if (!publicKey) { console.log('error', 'Wallet not connected!'); notify({ type: 'error', message: 'error', description: 'Wallet not connected!' }); return; } let signature: TransactionSignature = ''; try { const secret = [my private key]; // Replace with your secret const fromKeypair = Keypair.fromSecretKey(new Uint8Array(secret)); const communityId = 123; const communityName = "MyCommunity"; const parentCommunityId = 456; const tags = []; const chainID = 789; const chainName = "Solana"; const dataArray = [ communityId.toString(), communityName.toString(), parentCommunityId.toString(), tags.join(','), chainID.toString(), chainName.toString() ]; const concatenatedData = dataArray.join(''); const { publicKey, signTransaction, signAllTransactions } = useWallet(); const signerWallet = { publicKey: publicKey, signTransaction: signTransaction, signAllTransactions: signAllTransactions, }; const provider = new anchor.Provider( connection, signerWallet, { commitment: "processed" } ); const votingProgram = new PublicKey('my public key'); const program = new anchor.Program( PROGRAM_ABI, votingProgram, provider ); const accounts = await program .account .votingProgram .fetch(fromKeypair.publicKey) console.log(accounts) } catch (error: any) { notify({ type: 'error', message: `Account gathering failed!`, description: error?.message, txid: signature }); console.log( 'error', `Account gathering failed! ${error?.message}`, signature ); } }, [publicKey, connection]); return ( <div className="flex flex-row justify-center"> <div className="relative group items-center"> <div className="m-1 absolute -inset-0.5 bg-gradient-to-r from-indigo-500 to-fuchsia-500 rounded-lg blur opacity-20 group-hover:opacity-100 transition duration-1000 group-hover:duration-200 animate-tilt" ></div> <button className="px-8 m-2 btn animate-pulse bg-gradient-to-br from-indigo-500 to-fuchsia-500 hover:from-white hover:to-purple-300 text-black" onClick={onClick} > <span>Contract Accounts</span> </button> </div> </div> ); };
解决方案
1. 修复Hook调用违规问题
错误根源是在useCallback内部调用了useWallet(),违反React Hooks核心规则:Hooks必须在函数组件的顶层作用域调用,不能嵌套在回调函数、循环或条件语句中。
修改方式:
- 将
useWallet()移到组件顶层,直接解构所需的signTransaction和signAllTransactions; - 在
onClick回调中直接使用这些已解构的变量,无需再次调用Hook。
2. 优化ABI加载逻辑
当前全局变量加载ABI的方式可能导致异步加载未完成时PROGRAM_ABI为空,改用React状态管理确保ABI加载完成后再执行业务逻辑:
- 使用
useState存储ABI数据和加载状态; - 使用
useEffect触发ABI加载,避免重复请求。
修改后的完整代码
import { useState, useEffect, useCallback } from 'react'; import { FC } from 'react'; import { useConnection, useWallet } from '@solana/wallet-adapter-react'; import { useUserSOLBalanceStore } from './path-to-your-store'; // 替换为实际路径 import { Keypair, PublicKey, TransactionSignature } from '@solana/web3.js'; import * as anchor from '@project-serum/anchor'; import { notify } from './path-to-notify'; // 替换为实际路径 export const GetContractAccounts: FC = () => { const { connection } = useConnection(); // 顶层解构useWallet的所有需要的变量 const { publicKey, signTransaction, signAllTransactions } = useWallet(); const { getUserSOLBalance } = useUserSOLBalanceStore(); // 用状态存储ABI,替代全局变量 const [programAbi, setProgramAbi] = useState<any>(null); const [isAbiLoaded, setIsAbiLoaded] = useState(false); // 加载ABI的逻辑移到useEffect中 useEffect(() => { const loadAbi = async () => { try { const response = await fetch('../deployment_data/xxx.json'); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); setProgramAbi(data); setIsAbiLoaded(true); console.log('PROGRAM_ABI:', data); } catch (error) { console.error('Error fetching JSON:', error); notify({ type: 'error', message: 'ABI加载失败', description: (error as Error).message }); } }; loadAbi(); }, []); const onClick = useCallback(async () => { if (!publicKey) { console.log('error', 'Wallet not connected!'); notify({ type: 'error', message: '错误', description: '钱包未连接!' }); return; } if (!isAbiLoaded || !programAbi) { notify({ type: 'error', message: '错误', description: 'ABI尚未加载完成,请稍后重试' }); return; } let signature: TransactionSignature = ''; try { const secret = [1,2,3...]; // 替换为你的私钥数组 const fromKeypair = Keypair.fromSecretKey(new Uint8Array(secret)); // 直接使用顶层解构的signer变量 const signerWallet = { publicKey, signTransaction, signAllTransactions, }; const provider = new anchor.Provider( connection, signerWallet, { commitment: "processed" } ); const votingProgram = new PublicKey('替换为你的程序公钥'); const program = new anchor.Program( programAbi, votingProgram, provider ); const accounts = await program.account.votingProgram.fetch(fromKeypair.publicKey); console.log('账户数据:', accounts); notify({ type: 'success', message: '账户获取成功', description: '已成功获取合约账户数据' }); } catch (error: any) { notify({ type: 'error', message: `账户获取失败!`, description: error?.message, txid: signature }); console.log( 'error', `账户获取失败!${error?.message}`, signature ); } }, [publicKey, connection, signTransaction, signAllTransactions, isAbiLoaded, programAbi]); return ( <div className="flex flex-row justify-center"> <div className="relative group items-center"> <div className="m-1 absolute -inset-0.5 bg-gradient-to-r from-indigo-500 to-fuchsia-500 rounded-lg blur opacity-20 group-hover:opacity-100 transition duration-1000 group-hover:duration-200 animate-tilt" ></div> <button className="px-8 m-2 btn animate-pulse bg-gradient-to-br from-indigo-500 to-fuchsia-500 hover:from-white hover:to-purple-300 text-black" onClick={onClick} disabled={!isAbiLoaded} // ABI未加载时禁用按钮 > <span>{isAbiLoaded ? '获取合约账户' : 'ABI加载中...'}</span> </button> </div> </div> ); };
额外注意事项
- 私钥直接硬编码在代码中存在严重安全风险,生产环境需通过环境变量或安全注入方式传递;
- 代码中
concatenatedData变量未实际使用,可考虑移除; - 检查
react和react-dom版本是否一致,避免版本不匹配引发Hook错误; - 执行
npm ls react查看依赖树,确认项目中不存在多份React副本,如有需统一版本。
内容的提问来源于stack exchange,提问作者Ferda-Ozdemir-Sonmez
相关产品推荐
相关产品推荐

