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

React使用useEffect/useState请求数据延迟 需两次点击提交才显示正确结果

问题原因

你遇到的点击两次提交才返回正确结果的问题,和网络延迟无关,完全是React状态更新时序+闭包导致的逻辑错误:

  • React的setState是异步批处理的,你在handleSubmit里调用setInputCurrency、setInputAmount、setOutputCurrency之后,当前函数作用域里拿到的inputAmount、exchangeRate还是上一次渲染留存的旧值,第一次点击时用旧值计算结果,必然不对。
  • 你把汇率请求逻辑放在了依赖inputCurrency、outputCurrency的useEffect里,第一次点击提交更新货币选择后,组件要等重渲染完成才会执行effect发请求拿新汇率,等新汇率返回时,你早就用旧汇率算完结果了——相当于第一次点击只完成了"更新选中的货币类型、触发新汇率请求",第二次点击时所有状态都已经更新成最新值,才能算出正确结果。
  • 现有effect还有个隐藏bug:请求回调里取data.conversion_rates[outputCurrency]时,拿到的是effect触发当次闭包里的旧outputCurrency,哪怕请求发对了,取到的汇率也可能和你刚选的输出货币不匹配。
修复方案

调整逻辑时序,不要等状态更新后再被动触发请求、计算结果,提交时直接读取表单最新值拉取汇率,一次性更新所有状态即可,修改后的完整代码如下:

import './App.css';
import { useState } from 'react';
import { Select, Button } from '@chakra-ui/react';

function App() {
  const [inputCurrency, setInputCurrency] = useState('USD');
  const [inputAmount, setInputAmount] = useState(1);
  const [outputCurrency, setOutputCurrency] = useState('HKD');
  const [outputAmount, setOutputAmount] = useState(7.8491);

  const handleSubmit = async (e) => {
    e.preventDefault();
    // 直接读取表单当前提交的最新值,不依赖异步更新的state
    const curInputCurrency = e.target.inputCurrency.value;
    const curInputAmount = Number(e.target.inputAmount.value);
    const curOutputCurrency = e.target.outputCurrency.value;

    try {
      // 用最新选中的输入货币拉取汇率
      const res = await fetch(`${process.env.REACT_APP_DATA_SOURCE}${curInputCurrency}`);
      const data = await res.json();
      const curRate = data.conversion_rates[curOutputCurrency];
      // 一次性更新所有状态,用最新汇率计算结果
      setInputCurrency(curInputCurrency);
      setInputAmount(curInputAmount);
      setOutputCurrency(curOutputCurrency);
      setOutputAmount(curInputAmount * curRate);
    } catch (err) {
      console.log('汇率获取失败:', err);
    }
  };

  return (
    <div className="main-body">
      <form onSubmit={handleSubmit}>
        <h3>Set input amount:</h3>
        <input className="inputAmount" name="inputAmount" type="number" defaultValue={1} />

        <h3>Set input currency:</h3>
        <Select name="inputCurrency" defaultValue="USD">
          <option value="USD">USD</option>
          <option value="HKD">HKD</option>
          <option value="CNY">CNY</option>
          <option value="GBP">GBP</option>
          <option value="CAD">CAD</option>
        </Select>

        <h3>Set output currency:</h3>
        <Select name="outputCurrency" defaultValue="HKD">
          <option value="USD">USD</option>
          <option value="HKD">HKD</option>
          <option value="CNY">CNY</option>
          <option value="GBP">GBP</option>
          <option value="CAD">CAD</option>
        </Select>

        <Button colorScheme="blue" type="submit">
          Submit
        </Button>
      </form>

      <h3>
        {inputAmount} {inputCurrency} is equal to:
      </h3>
      <h3>
        {outputAmount} {outputCurrency}
      </h3>
    </div>
  );
}

export default App;
核心改动说明
  • 删掉了多余的useEffect汇率请求逻辑,不再依赖状态变化触发请求,从根源避免闭包拿旧值、状态更新时序错乱的问题
  • 提交时直接读取表单的最新值发请求、算结果,所有状态一次性同步更新,点击一次提交就能拿到正确结果
  • 给输入框和下拉选择加了defaultValue匹配初始状态,页面加载后表单默认值和展示结果一致
  • 把原来混用await和.then的请求写法改成了统一的async/await语法,减少链式调用的闭包隐患

如果你需要实现"切换货币时不点击提交就自动更新汇率"的效果,记得把输出金额的计算逻辑也放到useEffect里,等汇率请求返回后再计算结果,不要在提交事件里依赖旧state做计算。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 05:15:18