React中Context存储的钱包地址首次跳转后变为null的问题排查
React Context中web3钱包地址首次路由跳转后丢失的问题
问题描述
在React项目中,通过Context管理web3钱包地址状态,使用react-router-dom实现路由。用户首次连接钱包后跳转至/explore页面时,Context内的walletAddress状态会短暂存在后变为null,导致依赖该状态的UI组件异常;但**断开钱包后重新连接(不刷新页面)**时,应用功能恢复正常。已尝试将Context Provider包裹整个App组件,问题仍未解决。
相关代码
1. WalletContext.js
import { createContext, useState, useEffect } from 'react'; import { ethers } from 'ethers'; export const WalletContext = createContext(); export const WalletProvider = ({ children }) => { const [walletAddress, setWalletAddress] = useState(null); const connectWallet = async () => { if (!window.ethereum) return alert('请安装MetaMask'); try { const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); setWalletAddress(accounts[0]); } catch (err) { console.error('连接钱包失败:', err); } }; const disconnectWallet = () => { setWalletAddress(null); }; useEffect(() => { if (!window.ethereum) return; const handleAccountsChanged = (accounts) => { if (accounts.length > 0) { setWalletAddress(accounts[0]); } else { setWalletAddress(null); } }; window.ethereum.on('accountsChanged', handleAccountsChanged); return () => { window.ethereum.removeListener('accountsChanged', handleAccountsChanged); }; }, []); return ( <WalletContext.Provider value={{ walletAddress, connectWallet, disconnectWallet }}> {children} </WalletContext.Provider> ); };
2. App.jsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { WalletProvider } from './contexts/WalletContext'; import Home from './pages/Home'; import Explore from './pages/Explore'; import Navbar from './components/Navbar'; function App() { return ( <WalletProvider> <Router> <Navbar /> <Routes> <Route path="/" element={<Home />} /> <Route path="/explore" element={<Explore />} /> </Routes> </Router> </WalletProvider> ); } export default App;
3. Home.jsx(连接钱包并跳转页面)
import { useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import { WalletContext } from '../contexts/WalletContext'; const Home = () => { const { connectWallet } = useContext(WalletContext); const navigate = useNavigate(); const handleConnectAndNavigate = async () => { await connectWallet(); navigate('/explore'); }; return ( <div> <button onClick={handleConnectAndNavigate}>连接钱包并进入探索页面</button> </div> ); }; export default Home;
4. Explore.jsx(依赖walletAddress的页面)
import { useContext } from 'react'; import { WalletContext } from '../contexts/WalletContext'; const Explore = () => { const { walletAddress } = useContext(WalletContext); if (!walletAddress) { return <div>请先连接钱包</div>; } return ( <div> <h2>探索页面</h2> <p>当前钱包地址: {walletAddress}</p> </div> ); }; export default Explore;
问题分析与解决方案
1. 异步状态更新与路由跳转的时序冲突
首次连接时,connectWallet的异步操作完成后立即执行navigate,但React的状态更新是异步的——此时walletAddress的新值还未同步到Context中,跳转后Explore组件先读取到旧的null值;后续accountsChanged事件触发时,可能因为路由跳转导致组件挂载时机问题,意外将状态重置。
修复方案:等待状态更新完成后再跳转
修改Home组件,通过监听walletAddress状态变化来触发跳转:
import { useContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { WalletContext } from '../contexts/WalletContext'; const Home = () => { const { connectWallet, walletAddress } = useContext(WalletContext); const navigate = useNavigate(); const [triggerNavigate, setTriggerNavigate] = useState(false); const handleConnect = async () => { await connectWallet(); setTriggerNavigate(true); }; useEffect(() => { if (triggerNavigate && walletAddress) { navigate('/explore'); setTriggerNavigate(false); } }, [triggerNavigate, walletAddress, navigate]); return ( <div> <button onClick={handleConnect}>连接钱包并进入探索页面</button> </div> ); }; export default Home;
2. 初始化时未读取已连接的钱包地址
首次页面加载时,Context没有主动读取钱包当前的已连接地址,仅依赖accountsChanged事件更新状态,导致首次连接后跳转的间隙状态未同步。
修复方案:在Provider初始化时主动读取地址
修改WalletProvider的初始化逻辑:
export const WalletProvider = ({ children }) => { const [walletAddress, setWalletAddress] = useState(null); useEffect(() => { if (!window.ethereum) return; // 初始化时读取已连接的地址 const fetchCurrentAddress = async () => { const accounts = await window.ethereum.request({ method: 'eth_accounts' }); if (accounts.length > 0) { setWalletAddress(accounts[0]); } }; fetchCurrentAddress(); const handleAccountsChanged = (accounts) => { setWalletAddress(accounts.length > 0 ? accounts[0] : null); }; window.ethereum.on('accountsChanged', handleAccountsChanged); return () => { window.ethereum.removeListener('accountsChanged', handleAccountsChanged); }; }, []); // ...其余connectWallet、disconnectWallet逻辑不变 };
3. 检查Context实例唯一性
确保所有组件导入的是同一个WalletContext实例,避免因复制代码导致多个createContext调用,造成状态隔离。
验证步骤
- 在
setWalletAddress和Explore组件中添加console.log,观察状态变化的时序; - 检查
accountsChanged事件在首次连接时的触发时机,确认是否存在重复更新状态的情况。
内容的提问来源于stack exchange,提问作者AS11
相关产品推荐
相关产品推荐

