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

Redux Reducer执行成功后组件未获最新状态,登录跳转异常

登录功能跳转异常问题

点击登录按钮时,Redux DevTools显示Reducer已正确更新isAuthenticated状态为true,但登录页面无法立即跳转至首页,需第二次点击才生效。同时存在现象:handleLogin函数外部的isAuthenticated输出true,但函数内部该值为false。


问题根源

核心原因是闭包捕获的旧状态快照:组件渲染时,isAuthenticated的值被闭包固定在当前渲染周期的状态中。即使dispatch完成后Redux状态更新,当前handleLogin函数执行上下文里的isAuthenticated仍为旧值(false),因此第一次点击不会触发跳转;第二次点击时,组件已因状态更新重新渲染,闭包捕获了新的true值,才会执行跳转逻辑。


解决方案

方案1:用useEffect监听状态变化(推荐)

遵循React响应式设计原则,通过useEffect监听isAuthenticated的变化,状态变为true时自动跳转:

export default function Login() {
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);

  // 监听状态变化触发跳转
  useEffect(() => {
    if (isAuthenticated) {
      navigate("/");
    }
  }, [isAuthenticated, navigate]);

  const handleLogin = async (credentials) => {
    await dispatch(loginUser(credentials));
    // 移除函数内的判断逻辑,交给useEffect处理
  };

  // 其余代码保持不变
}

方案2:直接从store获取最新状态(临时方案,不推荐)

在handleLogin内部通过store.getState()直接获取Redux最新状态,绕过闭包的旧值限制:

import store from "../../store/store"; // 确保已导入store

const handleLogin = async (credentials) => {
  await dispatch(loginUser(credentials));
  // 直接获取store最新状态
  const { isAuthenticated } = store.getState().auth;
  if (isAuthenticated) {
    navigate("/");
  }
};

方案对比

  • 方案1符合React状态驱动渲染的设计理念,副作用统一通过useEffect处理,代码可维护性高。
  • 方案2直接操作store,破坏了组件与Redux的绑定逻辑,不利于后续维护和测试,仅作为临时应急方案使用。

修改后的完整Login组件代码

import React, { useEffect } from "react";
import "./login.css";
import store from "../../store/store";

import { Form, Formik, Field } from "formik";
import { TextField, Button, CircularProgress } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";

import { loginUser } from "../../store/auth/auth.actions";

import * as Yup from "yup";

export default function Login() {
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);

  useEffect(() => {
    if (isAuthenticated) {
      navigate("/");
    }
  }, [isAuthenticated, navigate]);

  const handleLogin = async (credentials) => {
    await dispatch(loginUser(credentials));
  };

  const validationSchema = Yup.object().shape({
    username: Yup.string().required("Username is required"),
    password: Yup.string().required("Password is required"),
  });

  return (
    <main className="login-page">
      <section className="picture-container">
        <img
          src={process.env.PUBLIC_URL + "/images/login.jpg"}
          alt="decoration"
        />
      </section>
      <section className="login-section">
        <section className="login-title">
          <h1>Login</h1>
          <h3>Log into CMUniversity</h3>
        </section>
        <Formik
          initialValues={{ username: "", password: "" }}
          validationSchema={validationSchema}
          initialErrors={{ username: "" }}
          onSubmit={async (values) => {
            await handleLogin(values);
          }}
        >
          {({ errors, touched, isValid }) => (
            <Form className="login-form">
              <Field
                as={TextField}
                type="text"
                label="Username"
                name="username"
                error={touched.username && !!errors.username}
                helperText={touched.username && errors.username}
                required
                className="form-group"
              />
              <Field
                as={TextField}
                type="password"
                label="Password"
                name="password"
                error={touched.password && !!errors.password}
                helperText={touched.password && errors.password}
                className="form-group"
                required
              />
              <span className="credentials">
                Forgot your <span className="active">username</span> or{" "}
                <span className="active">password</span>
              </span>
              <Button
                type="submit"
                variant="contained"
                className="submitButton"
                disabled={!isValid}
              >
                Log In
              </Button>
              <section className="social-login">
                <p>Or log in using: </p>
                <Button
                  type="button"
                  variant="contained"
                  className="googleButton"
                >
                  <img
                    src={process.env.PUBLIC_URL + "/images/google.png"}
                    alt="Sign in with Google"
                  />
                </Button>
                <Button
                  type="button"
                  variant="contained"
                  className="facebookButton"
                >
                  <img
                    src={process.env.PUBLIC_URL + "/images/facebook.png"}
                    alt="Sign in with Facebook"
                  />
                </Button>
              </section>
              <span className="credentials">
                Not a member yet?&nbsp;
                <span className="active">Sign Up Now</span>{" "}
              </span>
            </Form>
          )}
        </Formik>
      </section>
    </main>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:54:58