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

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;

问题原因与解决方案

核心问题

  1. 路由逻辑不匹配:当前PrivateRoute使用<Outlet />作为已登录时的返回值,但它是作为Route的element直接包裹页面组件,并非嵌套路由的父组件,导致<Outlet />没有对应的子路由内容可以渲染。
  2. 状态不同步:登录成功后,PrivateRoute未同步全局上下文的isLoggedIn状态,无法触发重渲染显示页面内容。
  3. 未登录时的返回值缺失:未登录时仅返回弹窗,没有保留路由上下文,导致弹窗关闭后页面无内容。

修改步骤

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 12:02:34