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

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;

问题分析与修复方案

核心问题

  1. Login组件跳转时机错误:调用dispatch(loginUser)后立即执行navigate,但loginUser是异步action,此时Redux状态还未更新,导致ProtectedRoute首次渲染时获取不到用户数据。
  2. ProtectedRoute冗余状态与错误判断:额外维护了activeUser状态,导致Redux状态更新后无法同步;同时userSlice的state中没有success字段,判断逻辑无效。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 16:20:23