React Axios提交表单修改状态时请求挂起问题排查
问题
实现登录页面时要求请求处理期间禁用登录表单:
- 提交错误凭证时,表单会按预期禁用,请求返回401后表单重新启用
- 但再次提交不同凭证时,Axios请求会挂起,Promise永远不resolve,浏览器标签页卡死,必须关闭
后端已接收并响应请求,Axios请求拦截器触发,但响应拦截器未触发。移除setLoginDisabled调用后问题消失,但表单无法禁用,怀疑是hooks使用有误。
以下是登录页面代码:
import React, { useState } from 'react'; import { useNavigate, NavigateFunction } from 'react-router-dom'; import { MailOutlined, KeyOutlined } from '@ant-design/icons'; import { Button, Form, Input, Typography } from 'antd'; import Navbar from '../components/Navbar'; import { DASHBOARD_ROUTE } from '../data/constants'; import styles from './Login.module.css'; import axios from 'axios'; export interface LoginProps {} interface LoginCredentials { usernameOrEmail: string, password: string, } const { Title } = Typography; // login page const Login = (props: LoginProps): JSX.Element | null => { const [passwordVisible, setPasswordVisible] = useState<boolean>(false); const [loginDisabled, setLoginDisabled] = useState<boolean>(false); const navigate: NavigateFunction = useNavigate(); const handleLogin = (credentials: LoginCredentials): void => { if (loginDisabled) { return; } setLoginDisabled(true); axios.post( '/accounts/jwt/create/', { username: credentials.usernameOrEmail, password: credentials.password }, { baseURL: 'http://localhost:8000', headers: { 'Content-Type': 'application/json' } } ).then(() => navigate(DASHBOARD_ROUTE) ).catch((err: any) => { console.log(err); setLoginDisabled(false); }); }; return ( <div> <Navbar selectedKey='login'> <div className={styles.mainContainer}> <Title>Login</Title> <Form name='basic' layout={'vertical'} initialValues={{ remember: true }} onFinish={handleLogin} disabled={loginDisabled} > <Form.Item label='Username/Email' name='usernameOrEmail' rules={[{ required: true, message: 'Please enter your username/email' }]} > <Input name="usernameOrEmail" placeholder='Enter username/email' prefix={<MailOutlined />} /> </Form.Item> <Form.Item label='Password' name='password' rules={[{ required: true, message: 'Please enter your password' }]} > <Input.Password name="password" placeholder='Enter password' prefix={<KeyOutlined />} visibilityToggle={{ visible: passwordVisible, onVisibleChange: setPasswordVisible }} /> </Form.Item> <Form.Item wrapperCol={{ offset: 8, span: 16 }}> <Button type='primary' htmlType='submit' > Login </Button> </Form.Item> </Form> </div> </Navbar> </div> ); } export default Login;
预期流程:
handleLogin发送请求前将loginDisabled设为true,表单禁用- Axios请求收到响应后将
loginDisabled设为false,表单恢复可用
分析与解决
问题根源
核心问题是Ant Design的Form组件在设置disabled为true时,会中断表单内部的状态更新逻辑,导致Axios的Promise链无法正常执行。具体来说:
- 第一次提交后,
setLoginDisabled(true)触发组件重渲染,Form被禁用 - 后端返回401后,
setLoginDisabled(false)重新启用表单,但此时Form内部的某些异步状态(比如提交状态)没有正确重置 - 第二次提交时,Form的提交逻辑和Axios请求的回调之间出现状态冲突,导致Promise永远挂起,最终阻塞UI
修复方案
不要直接给整个Form设置disabled属性,而是单独禁用提交按钮,同时保留handleLogin开头的重复提交判断,这样既可以防止重复提交,又不会破坏Form的内部状态:
修改后的关键代码:
const Login = (props: LoginProps): JSX.Element | null => { const [passwordVisible, setPasswordVisible] = useState<boolean>(false); const [loginDisabled, setLoginDisabled] = useState<boolean>(false); const navigate: NavigateFunction = useNavigate(); const handleLogin = (credentials: LoginCredentials): void => { if (loginDisabled) { return; } setLoginDisabled(true); axios.post( '/accounts/jwt/create/', { username: credentials.usernameOrEmail, password: credentials.password }, { baseURL: 'http://localhost:8000', headers: { 'Content-Type': 'application/json' } } ).then(() => { navigate(DASHBOARD_ROUTE); }).catch((err: any) => { console.log(err); }).finally(() => { setLoginDisabled(false); }); }; return ( // ... 其他部分保持不变 <Button type='primary' htmlType='submit' disabled={loginDisabled} // 仅禁用提交按钮 > Login </Button> // ... 其他部分保持不变 ); }
额外优化
使用finally块统一重置loginDisabled状态,确保无论请求成功、失败还是出现异常,按钮都能恢复可用状态,避免出现按钮永久禁用的情况。
内容的提问来源于stack exchange,提问作者Zahin Zaman
相关产品推荐
相关产品推荐

