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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 21:56:59