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

两个空依赖useEffect首个未执行:原因排查与修复方案

问题与解决方案

问题

我声明了两个依赖项为空数组的useEffect,但排在第一位的useEffect始终无法执行;若调换两者顺序,仍是位于首位的useEffect不执行。代码如下:

import React, {createContext, useState, useEffect, useRef, useContext} from 'react'
import { ethers } from 'ethers'
import Web3Modal from 'web3modal'
import { useNavigate } from 'react-router-dom'
import { ABI, ADDRESS } from '../contract'

const GlobalContext = createContext()

export const GlobalContextProvider = ({children})=>{
    const [walletAddress, setWalletAddress] = useState('')
    const [provider, setprovider] = useState('')
    const [contract, setcontract] = useState(null)
    const [showAlert, setshowAlert] = useState({status: false, type: 'info', message: ''})

    const updateCurrentWalletAddress = async ()=>{
        const accounts = await window.ethereum.request({method : 'eth_requestAccounts'})
        console.log("Hello from updateCurrentWalletAddress")
        if(accounts) setWalletAddress(accounts[0]) 
    }
   
    useEffect(() => {
      updateCurrentWalletAddress()
      window.ethereum.on('accountsChanged', updateCurrentWalletAddress)
    }, [])

    useEffect(() => {
      const setsmartContractAndProvider = async ()=>{
        const web3Modal = new Web3Modal()
        const connection = await web3Modal.connect()
        const newProvider = new ethers.providers.Web3Provider(connection)
        const signer = newProvider.getSigner()
        console.log("Hello from setsmartContractAndProvider")
        console.log({signer})
        console.log({walletAddress})
        const newContract = new ethers.Contract(ADDRESS, ABI, signer)
        setprovider(newProvider)
        setcontract(newContract)
      }
      setsmartContractAndProvider()
    }, [])

    useEffect(() => {
      if(showAlert?.status){
        const timer = setTimeout(()=>{
          setshowAlert ({status: false, type: 'info', message: ''})
        }, [5000])
        return ()=> clearTimeout(timer)
      }
    }, [showAlert])
    
    return(
      <GlobalContext.Provider value={{
        contract, walletAddress, showAlert, setshowAlert
      }}>
        {children}
      </GlobalContext.Provider>
    )
}

export const useGlobalContext = ()=> useContext(GlobalContext)

原因分析

核心问题是两个同时触发的钱包授权请求冲突:

  • 第一个useEffect里的updateCurrentWalletAddress调用了window.ethereum.request({method: 'eth_requestAccounts'}),会触发钱包授权弹窗;
  • 第二个useEffect里的web3Modal.connect()本质上也会触发相同的钱包授权流程;
  • 浏览器一次只能处理一个钱包授权弹窗,当两个请求同时触发时,后触发的请求会覆盖或阻塞先触发的那个,导致第一个useEffect里的异步操作无法正常完成,甚至被浏览器直接忽略。

另外,updateCurrentWalletAddress没有错误处理逻辑,如果用户拒绝授权,后续的setWalletAddress也不会执行,但这不是导致第一个useEffect完全不执行的主因。

修复方案

将两个逻辑合并,避免重复发起钱包授权请求,同时保证流程的顺序性。可以通过Web3Modal获取连接后,直接从中提取钱包地址,无需单独调用eth_requestAccounts:

import React, {createContext, useState, useEffect, useContext} from 'react'
import { ethers } from 'ethers'
import Web3Modal from 'web3modal'
import { ABI, ADDRESS } from '../contract'

const GlobalContext = createContext()

export const GlobalContextProvider = ({children})=>{
    const [walletAddress, setWalletAddress] = useState('')
    const [provider, setprovider] = useState('')
    const [contract, setcontract] = useState(null)
    const [showAlert, setshowAlert] = useState({status: false, type: 'info', message: ''})

    const updateCurrentWalletAddress = async (accounts) => {
        if(accounts) setWalletAddress(accounts[0]) 
    }
   
    useEffect(() => {
      const initWalletAndContract = async () => {
        // 统一通过Web3Modal获取连接,避免重复授权
        const web3Modal = new Web3Modal()
        const connection = await web3Modal.connect()
        const newProvider = new ethers.providers.Web3Provider(connection)
        
        // 从连接中获取钱包地址
        const accounts = await newProvider.listAccounts()
        updateCurrentWalletAddress(accounts)
        
        // 初始化合约
        const signer = newProvider.getSigner()
        const newContract = new ethers.Contract(ADDRESS, ABI, signer)
        setprovider(newProvider)
        setcontract(newContract)

        // 监听账户变化
        window.ethereum.on('accountsChanged', updateCurrentWalletAddress)
      }

      initWalletAndContract()

      // 清理监听事件,避免内存泄漏
      return () => {
        window.ethereum.removeListener('accountsChanged', updateCurrentWalletAddress)
      }
    }, [])

    useEffect(() => {
      if(showAlert?.status){
        const timer = setTimeout(()=>{
          setshowAlert ({status: false, type: 'info', message: ''})
        }, 5000)
        return ()=> clearTimeout(timer)
      }
    }, [showAlert])
    
    return(
      <GlobalContext.Provider value={{
        contract, walletAddress, showAlert, setshowAlert
      }}>
        {children}
      </GlobalContext.Provider>
    )
}

export const useGlobalContext = ()=> useContext(GlobalContext)

修复要点

  1. 合并逻辑到单个useEffect:避免同时发起多个授权请求,确保流程串行执行;
  2. 通过Web3Modal连接获取地址:利用newProvider.listAccounts()从已建立的连接中提取地址,无需单独调用eth_requestAccounts;
  3. 添加清理函数:移除accountsChanged的监听事件,避免组件卸载后内存泄漏;
  4. 简化冗余代码:去掉重复的授权请求逻辑,统一钱包连接入口。

内容的提问来源于stack exchange,提问作者Arpit Jethava

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 10:28:18