React组件报错:Rendered more hooks than during previous render 修复
修复React Hooks调用错误:Rendered more hooks than during the previous render
问题场景
开发FeaturedPool组件时,通过API获取APY最高的vault,找到目标池后尝试调用useVault获取用户持仓详情,触发了"Rendered more hooks than during the previous render"错误,错误出现在if (poolDetails?.id)条件判断内部调用useVault的代码段。
错误原因
React Hooks有严格的调用规则:所有Hook必须在组件的顶层作用域调用,不能嵌套在条件判断、循环、函数内部。React依赖Hook的调用顺序来维护状态关联,条件调用会导致每次渲染时Hook的数量或顺序不一致,从而触发该错误。
修复方案
方案1:将Hook移至组件顶层,依赖动态参数
把useVault移到组件顶层,传入poolDetails?.id作为参数,利用Hook的依赖更新机制,当poolDetails变化时自动重新获取数据。如果useVault不支持传入undefined,可通过默认值处理空参数场景。
方案2:封装子组件分离Hook逻辑
创建一个子组件,仅当poolDetails存在时才渲染,在子组件内部调用useVault,确保子组件的Hook始终在顶层调用,符合规则。
以下是方案1的具体实现:
完整修复代码
export const FeaturedPool = () => { // @ts-ignore const [loading, setLoading] = useState(true); const [APY, setAPY] = useState(''); const [Liquidity, setLiquidity] = useState(''); const [userPos, setUserPos] = useState('----'); const [tvlId, setTvlId] = useState<String | undefined>('----'); const [poolDetails, setPoolDetails] = useState<PoolInfo | undefined>(); // 修复:将useVault移至组件顶层,传入poolDetails?.id(空值用默认字符串兜底) const { userPosition } = useVault(poolDetails?.id || ''); // 监听userPosition变化,更新本地状态 useEffect(() => { if (userPosition) { setUserPos(userPosition); console.log(userPosition); } }, [userPosition]); useEffect(() => { // 修复:将poolsArray移至useEffect内部,避免闭包问题 const poolsArray: Record<string, string> = {}; let counter = 0; const filteredPools = POOLS?.filter((x) => x.stableCoins) || []; filteredPools.map((pool) => { if (!pool.targetedAPYId) return; poolsArray[pool.targetedAPYId] = ''; fetch('https://yields.llama.fi/chart/' + pool.targetedAPYId) .then((response) => response.json()) .then((res) => { let result = res.data.at(-1).apyReward.toFixed(2); poolsArray[pool.targetedAPYId] = result; counter++; // 修复:用filteredPools.length替代硬编码的3,适配不同数量的池 if (counter === filteredPools.length) { console.log('ALL FETCHED'); const arr = Object.values(poolsArray); const max = Math.max(...arr.map(Number)); const poolKey = Object.keys(poolsArray).find((key) => Number(poolsArray[key]) === max); if (poolKey) { const targetPool = POOLS.find((x) => x.targetedAPYId === poolKey); setPoolDetails(targetPool); if (targetPool?.tvlId) { setTvlId(targetPool.tvlId); fetchLiquidityData(targetPool.tvlId); } } setAPY(String(max)); setLoading(false); } }) .catch(err => { console.error('Fetch APY failed:', err); counter++; if (counter === filteredPools.length) { setLoading(false); } }); }); }, []); const wallet = useWallet(); const app = useApp(); const fetchLiquidityData = (liqId: string) => { fetch('https://yields.llama.fi/chart/' + liqId) .then((response) => response.json()) .then((res) => { let liq = (Math.abs(Number(res.data.at(-1).tvlUsd)) / 1.0e6).toFixed(2) + 'M'; setLiquidity(liq); }) .catch(err => console.error('Fetch liquidity failed:', err)); }; const handleVaultIcon = (title: string) => { return title.toLowerCase() === 'quick vault'; }; return ( <> {!loading ? (<p>Loaded</p>) : <p>Not Loaded</p>} </> ); };
额外优化点
- 将
poolsArray移至useEffect内部,避免组件渲染时重复创建及闭包问题 - 用
filteredPools.length替代硬编码的3,适配不同数量的稳定币池 - 增加fetch请求的错误捕获,避免请求失败时页面一直处于加载状态
内容的提问来源于stack exchange,提问作者Anaya Ammar
相关产品推荐
相关产品推荐

