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

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;

预期流程:

  1. handleLogin发送请求前将loginDisabled设为true,表单禁用
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 06:55:56