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

使用MUI Loading Button时,onClick中调用异步函数导致加载状态不更新的原因及解决方法

问题原因分析

你遇到的核心问题是React状态更新的异步批量特性,再加上代码里状态设置的时机冲突:

  1. React useState更新是异步的:当你在onClick回调里调用setDownloadLoading(true)后,React并不会立刻修改downloadLoading的值——这个更新会被放入队列,等到当前事件循环的所有同步代码执行完毕后才会处理。所以在紧接着的console.log(downloadLoading)和downloadFile调用中,你拿到的依然是更新前的false值,这是React的正常行为。

  2. 状态被快速覆盖:你的downloadFile函数最后不管请求成功还是失败,都会调用setDownloadLoading(false)。如果你的fetch请求完成得很快(比如本地服务响应极快),setDownloadLoading(false)会在setDownloadLoading(true)的状态更新还没来得及渲染到组件上时就执行,导致加载状态刚要生效就被取消,看起来像是状态始终为false。

另外,你代码里那个空依赖的useEffect其实没什么实际作用(它只会在组件卸载时把状态设为false),反而可能在某些场景下干扰状态逻辑,建议直接移除。

解决方案

最直接的修复方式是把setDownloadLoading(true)的调用移到downloadFile函数内部,确保状态设置和异步请求的流程完全同步:

export const Row: React.FC<IProps> = ({ record }: IProps): JSX.Element => {
  const [downloadLoading, setDownloadLoading] = useState<boolean>(false);

  const download = (content: any, fileName: string) => {
    const link = document.createElement("a");
    link.setAttribute("href", URL.createObjectURL(content));
    link.setAttribute("download", `${fileName}.pdf`);
    link.style.visibility = "hidden";
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  };

  const downloadFile = async (
    e: React.MouseEvent<HTMLElement>,
    id: string | undefined,
    fileName: string
  ) => {
    e.preventDefault();
    // 进入函数后立刻设置加载状态为true
    setDownloadLoading(true);
    try {
      const response = await fetch(
        `http://localhost:8080/download/id/` + id
      );
      if (!response.ok) {
        const value = await response.json();
        console.log("val", value);
      } else {
        const content = await response.blob();
        download(content, fileName);
      }
    } catch (e) {
      console.log("error", e);
    } finally {
      // 不管请求成功/失败,最后都统一关闭加载状态
      setDownloadLoading(false);
    }
  };

  return (
    <>
      <TableRow>
        <TableCell align="left" size="medium">
          {record.name}
        </TableCell>
        <TableCell size="small">
          <OutlinedCustomButton
            loading={downloadLoading}
            loadingIndicator={<CircularProgress color="inherit" size={20} />}
            loadingPosition="end"
            endIcon={<FileDownloadIcon />}
            fullWidth
            onClick={(e) => {
              downloadFile(e, record.id, record.name);
            }}
          >
            Download
          </OutlinedCustomButton>
        </TableCell>
      </TableRow>
    </>
  );
};

额外优化点

  • 把response.json().then(...)改成await response.json(),保持async/await的语法一致性,避免混合使用Promise链式调用。
  • 用finally块统一处理状态重置,确保不管请求成功还是失败,加载状态都会被关闭,避免出现按钮一直处于加载状态的情况。

这样修改后,点击按钮时会立刻触发加载状态,MUI的Loading Button会正常显示加载指示器,等到异步请求完成后再自动关闭加载状态,完全符合你的预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 18:58:11