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

React提交登录表单报Can't perform a React state update错误如何修复

问题根因

你的报错和useEffect的清理机制缺失、异步fetch请求没有中断逻辑直接相关,具体问题出在这几个地方:

  • 调用userContext.handleChange更新全局登录状态后,userAuth会变为true,此时Test组件渲染逻辑里的登录表单会被卸载,如果此时之前触发的fetch请求还没完成,后续请求完成后尝试调用setIsLoading、setError等状态更新方法,就会触发「已卸载组件不能更新状态」的报错,严重时直接导致页面白屏。
  • 3处异步请求(组件挂载时的登录状态校验fetch、submitHandler里的校验注册状态fetch、getUserType里的查用户类型fetch)都没有配置中断逻辑,也没有判断组件是否还挂载就直接更新状态。
  • 现有useEffect的清理函数是空的,没有释放异步任务资源。

修复方案

第一步:用AbortController中断fetch请求,新增挂载状态判断

修改后的完整代码如下:

const Test = (props) => {
  const [userLoginDetails, setUserLoginDetails] = useState({
    userName: "",
    userPassword: "",
    userID: "",
  });

  const [isLoading, setIsLoading] = useState(false);
  const [isLogIn, setIsLogin] = useState(false);
  const [isCompanyMember, setIsCompanyMember] = useState(false);
  const [error, setError] = useState(false);
  const [message, setMessage] = useState("");
  const userContext = useContext(UserContext);
  const userAuth = userContext.authed;

  // 存储所有fetch请求的AbortController实例
  const abortsRef = useRef([]);

  const onChangeHandler = (e) => {
    let { name, value } = e.target;
    let updatedState = { ...userLoginDetails };
    updatedState[name] = value;
    setUserLoginDetails(updatedState);
  };

  const getUserType = async () => {
    // 新建中断控制器
    const controller = new AbortController();
    abortsRef.current.push(controller);
    try {
      const bodyContent = {
        userName: userLoginDetails.userName,
      };
      const response = await fetch(`${URL}/members/${userLoginDetails.userID}`, {
        method: "post",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(bodyContent),
        credentials: "include",
        // 绑定中断信号
        signal: controller.signal
      });
      if (!response.ok) {
        setError(true);
        setIsLoading(false);
        setMessage("something went wrong! try again!");
        return;
      }
      const body = await response.json();
      userContext.handleChange(body.data.result, true, userLoginDetails.userName);
      setIsLogin(true);
      setError(false);
      setIsLoading(false);
      setMessage("");
    } catch (err) {
      // 如果是主动中断的请求,不处理报错
      if (err.name !== 'AbortError') {
        setError(true);
        setIsLoading(false);
        setMessage("connection problem");
      }
    }
  };

  const submitHandler = async (e) => {
    e.preventDefault();
    // 先中断之前所有未完成的请求,避免重复提交触发多次状态更新
    abortsRef.current.forEach(ctrl => ctrl.abort());
    abortsRef.current = [];

    setIsLoading(true);
    setError(false);

    const controller = new AbortController();
    abortsRef.current.push(controller);

    fetch(`someURL`, {
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        UserName: userLoginDetails.userName,
        PassWord: userLoginDetails.userPassword,
        Id: userLoginDetails.userID,
      }),
      signal: controller.signal
    })
      .then((response) => {
        return response.json();
      })
      .then((response) => {
        if (response) {
          setIsCompanyMember(true);
        } else {
          setError(true);
          setIsLoading(false);
          setMessage("combination of user,id an password didnt work!");
        }
      })
      .catch((err) => {
        if (err.name !== 'AbortError') {
          setError(true);
          setIsLoading(false);
          setMessage("connection problem");
        }
      });
  };

  useEffect(() => {
    const controller = new AbortController();
    abortsRef.current.push(controller);

    fetch(`${URL}/login/`, { 
      credentials: "include",
      signal: controller.signal
    })
      .then((response) => {
        return response.json();
      })
      .then((response) => {
        console.log(response);
        if (response.data.loggedIn === true) {
          setIsLogin(true);
          setError(false);
          setMessage("");
          userContext.handleChange(
            response.data.userType,
            true,
            response.data.userName
          );
        }
      })
      .catch(err => {
        if (err.name === 'AbortError') return;
        console.error(err);
      });
    return () => {
      // 组件卸载时中断所有未完成的请求
      abortsRef.current.forEach(ctrl => ctrl.abort());
    };
  }, []);

  useEffect(() => {
    if (isCompanyMember) {
      getUserType();
    }
  }, [isCompanyMember]);

  return (
    <nav>
      {!isLogIn && error && (
        <div role="alert" style={{ margin: "auto" }}>
          {message}
          <button
            type="button"
            className="close"
            data-dismiss="alert"
            aria-label="Close"
          >
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
      )}
      <ul>
        <li>
          {isLoading && <TinySpinner />}

          {!userAuth && (
            <form id={classes.login} onSubmit={submitHandler}>
              <div>
                <input
                  type="text"
                  name="userName"
                  id={classes.login_username}
                  placeholder="Username"
                  autoComplete="username"
                  onChange={onChangeHandler}
                  value={userLoginDetails.userName}
                ></input>
              </div>
              <div>
                <input
                  type="password"
                  name="userPassword"
                  id={classes.login_password}
                  placeholder="Password"
                  autoComplete="current-password"
                  onChange={onChangeHandler}
                  value={userLoginDetails.userPassword}
                ></input>
              </div>
              <div>
                <input
                  type="text"
                  name="userID"
                  id={classes.login_userid}
                  placeholder="ID"
                  autoComplete="id"
                  onChange={onChangeHandler}
                  value={userLoginDetails.userID}
                ></input>
              </div>
              <div>
                <input type="submit" value="Log in"></input>
              </div>
            </form>
          )}
        </li>
      </ul>
    </nav>
  );
};
export default Test;

第二步:额外优化

可以在提交按钮点击后暂时禁用提交按钮,避免用户重复点击触发多次请求:

<input type="submit" value="Log in" disabled={isLoading}></input>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 20:12:02