React TypeScript中MUI自定义LoadingButton无法触发表单onSubmit问题
问题描述
在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,避免按钮点击时直接禁用打断提交:
- 修改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> </> ); });
- 父组件中使用:
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
相关产品推荐
相关产品推荐

