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

React+Redux登录成功后跳转仪表盘闪回异常求助

React + Redux 登录功能异常排查与修复

问题现象

  • 登录页面初始渲染时,Redux中user值为null、isSuccess值为false;
  • 点击「Sign In」按钮后,仪表盘仅显示1秒就跳回登录页,此时user和isSuccess重置为初始值;
  • 连续点击登录按钮5-10次后,偶尔能成功停留在仪表盘。

相关代码

登录页面代码

const Login = () => {

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const { user, isError, isSuccess, isLoading, message } = useSelector(
    (state) => state.auth
  );

  useEffect(() => {
    if (user || isSuccess) {
      navigate("/dashboard");
    }
    dispatch(reset());
  }, [user, isSuccess, dispatch, navigate]);

  const Auth = (e) => {
    e.preventDefault();
    dispatch(LoginUser({ email, password }));
  };

  return (
    <div className="wrapper">
        <main className="authentication-content">
        <div className="container-fluid">
        <div className="authentication-card">
            <div className="card shadow rounded-0 overflow-hidden">
            <div className="row g-0">
                <div className="col-lg-6 bg-login d-flex align-items-center justify-content-center">
                <img src="assets/images/error/login-img.jpg" className="img-fluid" alt=""/>
                </div>
                <div className="col-lg-6">
                <div className="card-body p-4 p-sm-5">
                    <h5 className="card-title">Sign In</h5>
                    <p className="card-text mb-5">See your growth and get consulting support!</p>
                    <form onSubmit={Auth} className="form-body">
                    {isError && <p className="has-text-centered">{message}</p>}
                        <div className="row g-3">
                        <div className="col-12">
                            <label htmlFor="inputEmailAddress" className="form-label">Email Address</label>
                            <div className="ms-auto position-relative">
                            <div className="position-absolute top-50 translate-middle-y search-icon px-3"><i className="bi bi-envelope-fill"></i></div>
                            <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} className="form-control radius-30 ps-5" id="inputEmailAddress" placeholder="Email Address"/>
                            </div>
                        </div>
                        <div className="col-12">
                            <label htmlFor="inputChoosePassword" className="form-label">Enter Password</label>
                            <div className="ms-auto position-relative">
                            <div className="position-absolute top-50 translate-middle-y search-icon px-3"><i className="bi bi-lock-fill"></i></div>
                            <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} className="form-control radius-30 ps-5" id="inputChoosePassword" placeholder="Enter Password"/>
                            </div>
                        </div>
                        <div className="col-6">
                            <div className="form-check form-switch">
                            <input className="form-check-input" type="checkbox" id="flexSwitchCheckChecked" />
                            <label className="form-check-label" htmlFor="flexSwitchCheckChecked">Remember Me</label>
                            </div>
                        </div>
                        <div className="col-6 text-end">    <a href="authentication-forgot-password.html">Forgot Password ?</a>
                        </div>
                        <div className="col-12">
                            <div className="d-grid">
                            <button type="submit" className="btn btn-primary radius-30">{isLoading ? "Loading..." : "Sign In"}</button>
                            </div>
                        </div>
                        </div>
                    </form>
                </div>
                </div>
            </div>
            </div>
        </div>
        </div>
        </main>
    </div>
  )
}

export default Login;

AuthSlice代码

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

const initialState = {
    user: null,
    isError: false,
    isSuccess: false,
    isLoading: false,
    message: ""
}

export const LoginUser = createAsyncThunk("user/LoginUser", async(user, thunkAPI) => {
    try {
        const response = await axios.post('http://localhost:5001/login', {
            email: user.email,
            password: user.password
        });
        return response.data;
    } catch (error) {
        if(error.response){
            const message = error.response.data.msg;
            return thunkAPI.rejectWithValue(message);
        }
    }
});

export const getMe = createAsyncThunk("user/getMe", async(_, thunkAPI) => {
    try {
        const response = await axios.get('http://localhost:5001/me');
        return response.data;
    } catch (error) {
        if(error.response){
            const message = error.response.data.msg;
            return thunkAPI.rejectWithValue(message);
        }
    }
});

export const LogOut = createAsyncThunk("user/LogOut", async() => {
    await axios.delete('http://localhost:5001/logout');
});

export const authSlice = createSlice({
    name: "auth",
    initialState,
    reducers:{
        reset: (state) => initialState
    },
    extraReducers:(builder) =>{
        builder.addCase(LoginUser.pending, (state) =>{
            state.isLoading = true;
        });
        builder.addCase(LoginUser.fulfilled, (state, action) =>{
            state.isLoading = false;
            state.isSuccess = true;
            state.user = action.payload;
        });
        builder.addCase(LoginUser.rejected, (state, action) =>{
            state.isLoading = false;
            state.isError = true;
            state.message = action.payload;
        })

        // Get User Login
        builder.addCase(getMe.pending, (state) =>{
            state.isLoading = true;
        });
        builder.addCase(getMe.fulfilled, (state, action) =>{
            state.isLoading = false;
            state.isSuccess = true;
            state.user = action.payload;
        });
        builder.addCase(getMe.rejected, (state, action) =>{
            state.isLoading = false;
            state.isError = true;
            state.message = action.payload;
        })
    }
});

export const {reset} = authSlice.actions;
export default authSlice.reducer;

仪表盘页面代码

const Dashboard = () => {

  const dispatch = useDispatch();
  const navigate = useNavigate();
  const { isError } = useSelector((state) => state.auth);

  useEffect(() => {
    dispatch(getMe());
  }, [dispatch]);

  useEffect(() => {
    if (isError) {
      navigate("/");
    }
  }, [isError, navigate]);
  return (
    <Layout>
      <Welcome />
    </Layout>
  );
};

export default Dashboard;

问题根源与修复方案

1. 核心问题:登录页reset调用时机错误

登录页的useEffect在user/isSuccess变化时,先导航到仪表盘,随即调用dispatch(reset())直接清空Redux状态。导致仪表盘加载时getMe请求拿不到有效状态,触发isError后跳回登录页。

修复代码(登录页):

useEffect(() => {
  // 登录成功时导航,不执行reset
  if (user || isSuccess) {
    navigate("/dashboard");
    return;
  }
  // 仅登录失败时重置错误状态
  if (isError) {
    setTimeout(() => dispatch(reset()), 3000);
  }
}, [user, isSuccess, isError, dispatch, navigate]);

2. 辅助问题:axios未携带登录凭证

如果后端用Session认证,axios默认不会自动携带Cookie,导致/me请求返回未授权,触发isError。

修复代码(AuthSlice顶部):

// 配置axios自动携带跨域Cookie
axios.defaults.withCredentials = true;

同时确保后端CORS配置开启credentials: true。

3. 仪表盘逻辑优化

避免重复调用getMe,仅在无用户信息时验证身份:

const Dashboard = () => {
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const { isError, user, isSuccess } = useSelector((state) => state.auth);

  useEffect(() => {
    if (!user && !isSuccess) {
      dispatch(getMe());
    }
  }, [dispatch, user, isSuccess]);

  useEffect(() => {
    if (isError) {
      navigate("/");
      dispatch(reset());
    }
  }, [isError, navigate, dispatch]);

  return (
    <Layout>
      <Welcome />
    </Layout>
  );
};

4. AuthSlice状态管理优化

在登录失败、登出成功时自动重置状态,避免手动调用失误:

extraReducers:(builder) =>{
  // ... 原有LoginUser逻辑
  builder.addCase(LoginUser.rejected, (state, action) =>{
    state.isLoading = false;
    state.isError = true;
    state.message = action.payload;
    // 3秒后自动清除错误状态
    setTimeout(() => {
      state.isError = false;
      state.message = "";
    }, 3000);
  });

  // ... 原有getMe逻辑

  // 登出成功后重置状态
  builder.addCase(LogOut.fulfilled, (state) =>{
    state.user = null;
    state.isSuccess = false;
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 01:35:28