React Router中PrivateRoute包裹路由后内容不加载问题排查
问题
- 使用
<PrivateRoute>包裹路由(例如<PrivateRoute><GamePage /></PrivateRoute>)时,页面仅渲染页眉、页脚和导航栏,中间核心内容无法显示;移除<PrivateRoute>后内容可正常加载。 - 认证流程正常:访问私有路由时会弹出登录弹窗,登录成功后进入目标路由,但内容仍为空白。
- 怀疑问题是否与Layout组件和PrivateRoute组件都使用了
<Outlet />有关?
相关代码
Layout.jsx
import React, { useState, useEffect, useContext, useCallback } from "react"; import { Outlet, useNavigate } from "react-router-dom"; import Header from "./Header/header"; import NavBar from "./NavBar/navbar"; import Footer from "./Footer/footer"; import { Context } from "../App"; import LoginPopup from "../components/LoginPopup/loginPopup"; import LogoutConfirmation from "../components/LogoutConfirmation/LogoutConfirmation"; import LocalizationSelectorPopup from "../components/LocalizationSelectorPopup/LocalizationSelectorPopup"; import useLogin from "../hooks/useLogin"; import useLogout from "../hooks/useLogout"; import i18n from "i18next"; import useAuth from "../hooks/useAuth"; import "./layout.css"; // Ensure this file includes your styles const Layout = () => { const [isLoggedIn, setIsLoggedIn] = useContext(Context); const [showLoginPopup, setShowLoginPopup] = useState(false); const [showLogoutConfirmation, setShowLogoutConfirmation] = useState(false); const [hamburgerOpen, setHamburgerOpen] = useState(false); const [showLocalizationPopup, setShowLocalizationPopup] = useState(false); const [selectedLanguage, setSelectedLanguage] = useState("en"); const [isMobile, setIsMobile] = useState(window.innerWidth <= 900); const navigate = useNavigate(); const { login } = useLogin(); const { logout } = useLogout(); useAuth(); useEffect(() => { const storedLanguage = localStorage.getItem("selectedLanguage"); if (storedLanguage) { setSelectedLanguage(storedLanguage); i18n.changeLanguage(storedLanguage); } }, []); useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth <= 900); }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); const handleSignInClick = useCallback(() => { setShowLoginPopup(true); }, []); const handleCloseLoginPopup = useCallback(() => { setShowLoginPopup(false); }, []); const handleLogin = useCallback(async (username, password, rememberMe) => { const result = await login(username, password); if (result.success) { setIsLoggedIn(true); setShowLoginPopup(false); setHamburgerOpen(false); } else { console.error(result.message); return { message: result.message, type: "error" }; } }, [login, setIsLoggedIn]); const handleLogout = useCallback(() => { setShowLogoutConfirmation(true); }, []); const confirmLogout = useCallback(() => { localStorage.removeItem("accessToken"); localStorage.removeItem("signedInUsername"); setIsLoggedIn(false); navigate("/"); setHamburgerOpen(false); setShowLogoutConfirmation(false); logout(); }, [logout, navigate, setIsLoggedIn]); const cancelLogout = useCallback(() => { setShowLogoutConfirmation(false); }, []); const handleLanguageChange = useCallback((language) => { setSelectedLanguage(language); localStorage.setItem("selectedLanguage", language); i18n.changeLanguage(language); }, []); return ( <div> {showLoginPopup && ( <LoginPopup onClose={handleCloseLoginPopup} onLogin={handleLogin} /> )} {showLocalizationPopup && ( <LocalizationSelectorPopup setShowLocalizationPopup={setShowLocalizationPopup} setSelectedLanguage={handleLanguageChange} selectedLanguage={selectedLanguage} /> )} {showLogoutConfirmation && ( <LogoutConfirmation onConfirm={confirmLogout} onCancel={cancelLogout} /> )} <Header isLoggedIn={isLoggedIn} handleSignInClick={handleSignInClick} handleLogout={handleLogout} /> <NavBar isLoggedIn={isLoggedIn} handleSignInClick={handleSignInClick} handleLogout={handleLogout} hamburgerOpen={hamburgerOpen} setHamburgerOpen={setHamburgerOpen} setShowLocalizationPopup={setShowLocalizationPopup} selectedLanguage={selectedLanguage} /> <div className={`${selectedLanguage === "de" ? "for-german content" : isMobile ? "mobile-content" : "content"}`}> {<Outlet />} </div> <Footer /> </div> ); }; export default React.memo(Layout);
Router
import { React, useEffect } from "react"; import { Routes, Route, BrowserRouter as Router, useLocation } from "react-router-dom"; import Layout from "../layout/Layout"; import Home from "../pages/Home/home"; import Events from "../pages/Events/events"; import Casino from "../pages/Casino/casino"; import Sports from "../pages/Sports/sports"; import Poker from "../pages/Poker/poker"; import CashOut from "../pages/CashOut/CashOut"; import GamePage from "../pages/GamePage/GamePage"; import Terms from "../pages/Terms/terms"; import PrivateRoute from "../components/PrivateRoute/privateRoute"; import AutoLogin from "../pages/AutoLogin/AutoLogin"; import Category from "../pages/Category/Category"; function ScrollToTop() { const { pathname } = useLocation(); useEffect(() => { window.scrollTo(0, 0); }, [pathname]); return null; } const Routers = ({ activeCashOutPageTab, setActiveCashOutPageTab }) => { return ( <Router> <ScrollToTop /> <Routes> <Route path="/" element={<Layout />}> <Route index element={<Home />} /> <Route path="events" element={<Events />} /> <Route path="casino" element={<Casino />} /> <Route path="sports" element={<Sports />} /> <Route path="poker" element={<Poker />} /> <Route path="cash-out" element={<PrivateRoute><CashOut activeCashOutPageTab={activeCashOutPageTab} setActiveCashOutPageTab={setActiveCashOutPageTab} /></PrivateRoute>} /> <Route path="games/:gameName" element={<PrivateRoute><GamePage /></PrivateRoute>} /> <Route path="terms" element={<Terms />} /> <Route path="categories/:category" element={<Category />} /> <Route path="auto-login" element={<AutoLogin />} /> </Route> </Routes> </Router> ); }; export default Routers;
PrivateRoute.jsx
import React, { useEffect, useState } from "react"; import { useLocation, Navigate, Outlet } from "react-router-dom"; import useAuth from "../../hooks/useAuth"; import LoginPopup from "../LoginPopup/loginPopup"; import useLogin from '../../hooks/useLogin'; const PrivateRoute = () => { const isLoggedIn = useAuth(); const location = useLocation(); const [showLoginPopup, setShowLoginPopup] = useState(false); const { login } = useLogin(); useEffect(() => { if (!isLoggedIn) { setShowLoginPopup(true); } }, [isLoggedIn]); const handleLogin = async (username, password, rememberMe) => { const result = await login(username, password); if (result.success) { setShowLoginPopup(false); } else { console.error(result.message); return { message: result.message, type: "error" }; } }; if (isLoggedIn) { return <Outlet />; } else { return ( showLoginPopup && ( <LoginPopup onClose={() => setShowLoginPopup(false)} onLogin={handleLogin} /> ) ); } }; export default PrivateRoute;
问题原因与解决方案
核心问题
- 路由逻辑不匹配:当前
PrivateRoute使用<Outlet />作为已登录时的返回值,但它是作为Route的element直接包裹页面组件,并非嵌套路由的父组件,导致<Outlet />没有对应的子路由内容可以渲染。 - 状态不同步:登录成功后,
PrivateRoute未同步全局上下文的isLoggedIn状态,无法触发重渲染显示页面内容。 - 未登录时的返回值缺失:未登录时仅返回弹窗,没有保留路由上下文,导致弹窗关闭后页面无内容。
修改步骤
1. 重构PrivateRoute组件
让组件直接渲染传入的子组件,而非使用<Outlet />,同时同步全局登录状态:
import React, { useEffect, useState, useContext } from "react"; import { useLocation } from "react-router-dom"; import useAuth from "../../hooks/useAuth"; import LoginPopup from "../LoginPopup/loginPopup"; import useLogin from '../../hooks/useLogin'; import { Context } from "../../App"; const PrivateRoute = ({ children }) => { const isLoggedIn = useAuth(); const [_, setIsLoggedInContext] = useContext(Context); const location = useLocation(); const [showLoginPopup, setShowLoginPopup] = useState(false); const { login } = useLogin(); useEffect(() => { // 同步全局登录状态 setIsLoggedInContext(isLoggedIn); if (!isLoggedIn) { setShowLoginPopup(true); } else { setShowLoginPopup(false); } }, [isLoggedIn, setIsLoggedInContext]); const handleLogin = async (username, password, rememberMe) => { const result = await login(username, password); if (result.success) { setIsLoggedInContext(true); setShowLoginPopup(false); } else { console.error(result.message); return { message: result.message, type: "error" }; } }; if (isLoggedIn) { return children; } else { return ( <> {showLoginPopup && ( <LoginPopup onClose={() => setShowLoginPopup(false)} onLogin={handleLogin} /> )} </> ); } }; export default PrivateRoute;
2. 确保useAuth钩子能正确感知状态变化
修改useAuth,让它监听全局上下文或localStorage的变化:
import { useContext, useEffect, useState } from "react"; import { Context } from "../App"; const useAuth = () => { const [isLoggedIn, setIsLoggedIn] = useState(false); const [contextLoggedIn] = useContext(Context); useEffect(() => { const token = localStorage.getItem("accessToken"); setIsLoggedIn(!!token || contextLoggedIn); }, [contextLoggedIn]); return isLoggedIn; }; export default useAuth;
3. 保持原有路由配置不变
原Router中私有路由的写法无需修改,因为重构后的PrivateRoute会直接渲染传入的页面组件。
内容的提问来源于stack exchange,提问作者ScuffedCoder
相关产品推荐
相关产品推荐

