React集成Thirdweb时useContract获取合约为undefined问题求助
问题描述
开发的众筹平台此前运行正常,现出现异常:使用Thirdweb的useContract钩子获取合约(地址0x0Cf484c66E2493875D775Aeea8c426dfAD8d597b)时返回undefined,但该合约在Thirdweb平台上运行正常。
错误截图:
Context文件代码
import React, { useContext, createContext } from "react"; import { useAddress, useContract, useMetamask, useContractWrite } from "@thirdweb-dev/react"; import { ethers } from "ethers"; const StateContext = createContext(); export const StateContextProvider = ({ children }) => { const { contract } = useContract("0x0Cf484c66E2493875D775Aeea8c426dfAD8d597b") const { mutateAsync: createCampaign } = useContractWrite(contract, "createCampaign"); //line 12: storing the address of current wallet const address = useAddress(); console.log(address); const connect = useMetamask(); const publishCampaign = async (form) => { try { const data = await createCampaign([ address, //owner form.title, form.description, form.target, new Date(form.deadline).getTime(), //deadline form.image, ]) console.log("contract call success", data); } catch (error) { console.log("contract call failure", error); } } const getCampaigns = async () => { const campaigns = await contract.call("getCampaigns"); const parsedCampaigns = campaigns.map((campaign, i) => ({ owner: campaign.owner, title: campaign.title, description: campaign.description, target: ethers.utils.formatEther(campaign.target.toString()), deadline: campaign.deadline.toNumber(), amountCollected: ethers.utils.formatEther(campaign.amountCollected.toString()), image: campaign.image, pId: i })); return parsedCampaigns; } const getUserCampaigns = async () => { const allCampaigns = await getCampaigns(); const filteredCampaigns = allCampaigns.filter((campaign) => campaign.owner === address); return filteredCampaigns; } const donate = async (pId, amount) => { const data = await contract.call("donateToCampaign", pId, { value : ethers.utils.parseEther(amount)}); return data; } const getDonations = async (pId) => { const donations = await contract.call("getDonators", pId); const numberOfDonations = donations[0].length; const parsedDonations = []; for (let i = 0; i < numberOfDonations; i++) { parsedDonations.push({ donator: donations[0][i], donation: ethers.utils.formatEther(donations[1][i].toString()) }) } return parsedDonations; } return ( <StateContext.Provider value={{ address, contract, connect, createCampaign: publishCampaign, getCampaigns, getUserCampaigns, donate, getDonations }} > {children} </StateContext.Provider> ) } export const useStateContext = () => useContext(StateContext);
main.jsx代码
import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter as Router } from "react-router-dom"; import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react"; // import { Sepolia } from "@thirdweb-dev/chains"; import { StateContextProvider } from "./context"; import App from "./App"; import "./index.css"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <ThirdwebProvider desiredChainId={ChainId.Mumbai} activeChain ={ChainId.Mumbai}> <Router> <StateContextProvider> <App /> </StateContextProvider> </Router> </ThirdwebProvider> )
问题排查与解决方案
核心原因分析
useContract是异步钩子,组件初始渲染时合约实例还未加载完成,此时contract为undefined,直接传递给useContractWrite会触发报错,后续调用contract.call也会因空值失败。
具体修复步骤
1. 处理异步加载状态
useContract返回值包含isLoading状态,可用来判断合约是否加载完成,避免在未加载时执行依赖合约的操作:
const { contract, isLoading } = useContract("0x0Cf484c66E2493875D775Aeea8c426dfAD8d597b")
在Provider中可先判断isLoading,加载完成后再提供完整上下文值,或在子组件中处理加载状态。
2. 替换useContractWrite为手动合约调用
直接使用contract.prepare方法创建交易,避免依赖useContractWrite的初始化限制:
const publishCampaign = async (form) => { if (!contract || !address) return; try { const transaction = contract.prepare("createCampaign", [ address, form.title, form.description, ethers.utils.parseEther(form.target), // 转换为合约要求的wei单位 new Date(form.deadline).getTime(), form.image, ]); const data = await transaction.signAndSend(); console.log("contract call success", data); } catch (error) { console.log("contract call failure", error); } }
3. 为所有合约调用添加空值校验
在使用contract.call的方法中,先判断合约实例是否存在:
const getCampaigns = async () => { if (!contract) throw new Error("Contract instance not available"); const campaigns = await contract.call("getCampaigns"); // 后续处理逻辑 };
4. 确保链配置一致性
直接导入链对象替代ChainId,避免链ID配置错误:
import { Mumbai } from "@thirdweb-dev/chains"; // 替换ThirdwebProvider配置 <ThirdwebProvider activeChain={Mumbai}> {/* ... */} </ThirdwebProvider>
修改后的完整Context代码示例
import React, { useContext, createContext } from "react"; import { useAddress, useContract, useMetamask } from "@thirdweb-dev/react"; import { ethers } from "ethers"; const StateContext = createContext(); export const StateContextProvider = ({ children }) => { const { contract, isLoading } = useContract("0x0Cf484c66E2493875D775Aeea8c426dfAD8d597b") const address = useAddress(); const connect = useMetamask(); const publishCampaign = async (form) => { if (!contract || !address) return; try { const transaction = contract.prepare("createCampaign", [ address, form.title, form.description, ethers.utils.parseEther(form.target), new Date(form.deadline).getTime(), form.image, ]); const data = await transaction.signAndSend(); console.log("contract call success", data); } catch (error) { console.log("contract call failure", error); } } const getCampaigns = async () => { if (!contract) throw new Error("Contract instance not available"); const campaigns = await contract.call("getCampaigns"); const parsedCampaigns = campaigns.map((campaign, i) => ({ owner: campaign.owner, title: campaign.title, description: campaign.description, target: ethers.utils.formatEther(campaign.target.toString()), deadline: campaign.deadline.toNumber(), amountCollected: ethers.utils.formatEther(campaign.amountCollected.toString()), image: campaign.image, pId: i })); return parsedCampaigns; } const getUserCampaigns = async () => { if (!address) return []; const allCampaigns = await getCampaigns(); return allCampaigns.filter((campaign) => campaign.owner === address); } const donate = async (pId, amount) => { if (!contract) throw new Error("Contract instance not available"); const data = await contract.call("donateToCampaign", pId, { value : ethers.utils.parseEther(amount) }); return data; } const getDonations = async (pId) => { if (!contract) throw new Error("Contract instance not available"); const donations = await contract.call("getDonators", pId); const numberOfDonations = donations[0].length; const parsedDonations = []; for (let i = 0; i < numberOfDonations; i++) { parsedDonations.push({ donator: donations[0][i], donation: ethers.utils.formatEther(donations[1][i].toString()) }) } return parsedDonations; } if (isLoading) { return <div>Loading contract...</div>; } return ( <StateContext.Provider value={{ address, contract, connect, createCampaign: publishCampaign, getCampaigns, getUserCampaigns, donate, getDonations }} > {children} </StateContext.Provider> ) } export const useStateContext = () => useContext(StateContext);
额外注意点
- 原代码中
form.target直接以字符串传递,需转换为wei单位(ethers.utils.parseEther),否则合约会因参数类型不匹配报错。 - 执行需要钱包地址的操作前,需确保用户已连接钱包(
address不为undefined)。
内容的提问来源于stack exchange,提问作者20C4060_Srajan Acharya
相关产品推荐
相关产品推荐

