React Router v6 受保护路由登录时用户状态未更新问题求助
登录后ProtectedRoute首次渲染无法获取Redux用户状态的问题解决
问题描述
我正在实现用户登录功能:通过Login组件获取用户凭证,使用Redux Toolkit的userSlice处理身份验证并存储状态;配置ProtectedRoute组件校验用户登录状态,未登录则禁止访问Recipes页面。但登录后跳转至受保护路由时,在ProtectedRoute中调用useSelector钩子,首次渲染返回空用户对象,第二次渲染能获取到用户但登录仍失败,Redux DevTools显示状态已正常更新。我已使用useEffect并设置依赖数组,请问如何在ProtectedRoute首次渲染时获取到用户对象?
相关代码
Login.js(获取用户凭证并分发action)
import React, { useState } from 'react'; import { loginUser } from '../../features/users/userSlice'; import { useSelector, useDispatch } from 'react-redux' import { useNavigate } from 'react-router-dom'; export default function Login() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const user = useSelector(state => state.user.user) const dispatch = useDispatch() const navigate = useNavigate() return ( <div> <input type="text" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button type="submit" onClick={() => { dispatch(loginUser({email, password})) navigate('/recipes') }}>submit</button> </div> ) }
ProtectedRoute.js(校验用户登录状态)
import React, { useState, useEffect } from "react"; import { Navigate, Outlet } from "react-router-dom"; import { useSelector } from 'react-redux'; export default function ProtectedRoute({ children }) { const [ activeUser, setActiveUser ] = useState(false) const user = useSelector((state) => state.user); useEffect(() => { if (!user.isLoading) { user.success ? setActiveUser(true) : setActiveUser(false) console.log('active user: ' + activeUser) } }, [user]) return ( activeUser ? <Outlet /> : <Navigate to="/login"/> ) }
App.js(路由配置)
import React from "react"; import Recipes from "./components/recipes/recipes"; import Login from "./components/users/Login"; import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import ProtectedRoute from "./utils.js/ProtectedRoute"; const App = () => { return ( <div> <BrowserRouter> <Routes> <Route path="/login" element={<Login />} /> <Route path="/" element={<Navigate replace to="/login" />}/> <Route element={<ProtectedRoute />}> <Route element={<Recipes/>} path="/recipes" /> </Route> </Routes> </BrowserRouter> </div> ); }; export default App;
userSlice.js(Redux状态切片)
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import axios from "axios"; const loginUrl = 'http://localhost:5000/api/login'; const signupUrl = 'http://localhost:5000/api/signup'; export const loginUser = createAsyncThunk('user/loginUser', async (data) => { const response = await axios.post(loginUrl, data); return response; }) export const signupUser = createAsyncThunk('user/signupUser', async (data) => { const response = await axios.post(signupUrl, data); return response; }) const initialState = { user: {}, isLoading: true } const userSlice = createSlice({ name: 'user', initialState, reducers: { getPassword: (state, action) => { const password = action.payload console.log(password) } }, extraReducers: { [loginUser.pending]: (state) => { state.isLoading = false }, [loginUser.fulfilled]: (state, action) => { state.isLoading = false state.user = action.payload.data }, [loginUser.rejected]: (state) => { state.isLoading = false }, [signupUser.pending]: (state) => { state.isLoading = false }, [signupUser.fulfilled]: (state, action) => { state.isLoading = false state.user = action.payload.data }, [signupUser.rejected]: (state) => { state.isLoading = false }, } }) export const { getPassword } = userSlice.actions export default userSlice.reducer;
问题分析与修复方案
核心问题
- Login组件跳转时机错误:调用
dispatch(loginUser)后立即执行navigate,但loginUser是异步action,此时Redux状态还未更新,导致ProtectedRoute首次渲染时获取不到用户数据。 - ProtectedRoute冗余状态与错误判断:额外维护了
activeUser状态,导致Redux状态更新后无法同步;同时userSlice的state中没有success字段,判断逻辑无效。 - userSlice的isLoading逻辑颠倒:初始
isLoading为true,但pending时又设为false,导致加载状态判断混乱。
修复步骤
1. 修正Login组件:等待登录请求完成后跳转
// Login.js 修改点击事件处理逻辑 <button type="submit" onClick={async () => { try { await dispatch(loginUser({email, password})).unwrap(); // 登录成功后再跳转 navigate('/recipes'); } catch (err) { // 处理登录失败逻辑,比如提示错误 console.error('登录失败:', err); } }}>submit</button>
使用unwrap()获取异步action的最终结果,确保登录请求完成后再跳转,此时Redux状态已更新。
2. 简化ProtectedRoute:直接使用Redux状态判断
去掉冗余的activeUser状态,直接基于Redux中的用户数据和加载状态判断:
import React from "react"; import { Navigate, Outlet } from "react-router-dom"; import { useSelector } from 'react-redux'; export default function ProtectedRoute() { const { user, isLoading } = useSelector((state) => state.user); // 加载中时显示加载组件,避免直接跳转 if (isLoading) { return <div>加载中...</div>; } // 根据user是否有有效数据判断是否登录(比如存在id或email字段) const isAuthenticated = Object.keys(user).length > 0; return isAuthenticated ? <Outlet /> : <Navigate to="/login"/>; }
3. 修正userSlice的isLoading逻辑
调整初始状态和异步action的状态更新,确保加载状态正确:
const initialState = { user: {}, isLoading: false // 初始状态未加载,设为false } const userSlice = createSlice({ name: 'user', initialState, reducers: { getPassword: (state, action) => { const password = action.payload console.log(password) } }, extraReducers: { [loginUser.pending]: (state) => { state.isLoading = true // 请求开始时设为加载中 }, [loginUser.fulfilled]: (state, action) => { state.isLoading = false state.user = action.payload.data }, [loginUser.rejected]: (state) => { state.isLoading = false state.user = {} // 失败时清空用户数据 }, [signupUser.pending]: (state) => { state.isLoading = true }, [signupUser.fulfilled]: (state, action) => { state.isLoading = false state.user = action.payload.data }, [signupUser.rejected]: (state) => { state.isLoading = false state.user = {} }, } })
修复后效果
- 登录时等待请求完成再跳转,ProtectedRoute首次渲染就能获取到更新后的用户状态
- 去掉冗余状态,直接基于Redux状态判断,避免同步延迟
- 加载状态逻辑正确,用户体验更流畅
内容的提问来源于stack exchange,提问作者Haider Mansoor
相关产品推荐
相关产品推荐

