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

React TypeScript中MUI自定义LoadingButton无法触发表单onSubmit问题

自定义LoadingButton组件表单提交异常问题

问题描述

在React TypeScript项目中使用Material-UI(MUI)自定义LoadingButton组件时,将其设置为type="submit"并放置在带有onSubmit事件处理函数的表单内,点击按钮无法触发表单的onSubmit事件。但移除LoadingButton的handleClick函数中的setLoading(true)调用后,表单提交功能恢复正常。

组件代码

import { Button, ButtonProps, CircularProgress } from "@mui/material";
import React, {
  forwardRef,
  MouseEvent,
  useImperativeHandle,
  useState,
} from "react";

interface LoadingButtonWrapperProps extends ButtonProps {
  // 可添加其他自定义props
}

interface LoadingButtonWrapperHandle {
  disableLoading: () => void;
}

const LoadingButton = forwardRef<
  LoadingButtonWrapperHandle,
  LoadingButtonWrapperProps
>((props, ref) => {
  const [loading, setLoading] = useState<boolean>(false);

  // 向父组件暴露setLoading方法
  useImperativeHandle(ref, () => ({
    disableLoading,
  }));

  const disableLoading = () => {
    setLoading(false);
  };

  const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
    if (props.onClick) {
      props.onClick(e);
    }
    setLoading(true);
  };

  return (
    <>
      {loading ? <CircularProgress size={24} /> : null}
      <Button {...props} disabled={loading} onClick={handleClick}>
        {props.children}
      </Button>
    </>
  );
});

export default LoadingButton;

复现步骤

  • 创建带有onSubmit事件处理函数的表单;
  • 在表单内使用type="submit"的LoadingButton组件;
  • 点击按钮尝试提交表单。

预期行为

即使调用setLoading(true),点击LoadingButton也应正常触发表单的onSubmit事件。

实际行为

若LoadingButton的handleClick中调用setLoading(true),点击按钮不会触发表单的onSubmit事件。

问题原因及解决方案

原因

点击type="submit"按钮后,setLoading(true)会立即更新组件状态,导致Button被设置为disabled={loading}。浏览器在按钮变为禁用状态的瞬间,会中断表单的提交流程,因此onSubmit事件无法触发。

修复方案

方案1:延迟设置loading状态

通过setTimeout延迟设置loading,让浏览器有足够时间处理表单提交事件:

const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
  if (props.onClick) {
    props.onClick(e);
  }
  // 延迟0毫秒执行,让浏览器先处理表单提交
  setTimeout(() => {
    setLoading(true);
  }, 0);
};

方案2:由父组件控制loading状态(更推荐)

将loading状态的控制权交给表单所在的父组件,在表单onSubmit事件中设置loading,避免按钮点击时直接禁用打断提交:

  1. 修改LoadingButton组件,支持外部传入loading状态:
interface LoadingButtonWrapperProps extends ButtonProps {
  loading?: boolean;
}

const LoadingButton = forwardRef<
  LoadingButtonWrapperHandle,
  LoadingButtonWrapperProps
>((props, ref) => {
  // 使用外部传入的loading状态,保留内部状态作为 fallback
  const [internalLoading, setInternalLoading] = useState<boolean>(false);
  const loading = props.loading ?? internalLoading;

  useImperativeHandle(ref, () => ({
    disableLoading: () => setInternalLoading(false),
  }));

  const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
    if (props.onClick) {
      props.onClick(e);
    }
    // 仅当外部未传入loading时,内部控制loading
    if (props.loading === undefined) {
      setInternalLoading(true);
    }
  };

  return (
    <>
      {loading ? <CircularProgress size={24} /> : null}
      <Button {...props} disabled={loading} onClick={handleClick}>
        {props.children}
      </Button>
    </>
  );
});
  1. 父组件中使用:
const ParentComponent = () => {
  const [loading, setLoading] = useState(false);

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    setLoading(true);
    // 执行提交逻辑,完成后调用setLoading(false)
    fetch("/api/submit")
      .then(() => setLoading(false))
      .catch(() => setLoading(false));
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* 其他表单元素 */}
      <LoadingButton type="submit" loading={loading}>
        提交
      </LoadingButton>
    </form>
  );
};

方案3:避免直接禁用按钮(仅临时方案)

如果需要保留内部loading状态,可暂时移除disabled={loading},改为通过样式或其他方式提示加载状态,确保表单提交事件能正常触发:

// 修改Button的props,移除disabled,添加加载时的样式
<Button 
  {...props} 
  onClick={handleClick}
  disabled={props.disabled} // 仅使用外部传入的disabled,不使用内部loading
  sx={{ opacity: loading ? 0.7 : 1 }}
>
  {props.children}
</Button>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 04:35:59