Search code examples
javascriptreactjsreact-routerreact-router-dom

Private Route Content not Loading


I have an issue with my layout where if I have a PrivateRoute around my route i.e. <PrivateRoute><GamePage /></PrivateRoute> then theres no content displayed. The layout renders i.e. the header/footer/nav bar but not the actual center content. If I remove the PrivateRoute it loads fine.

Could it be something to do with my layout using {<Outlet />} and also the PrivateRoute returning {<Outlet />} if I log in successfully?

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;

The authentication side works fine i.e. I try go to that private route page and the login popup appears. If I log in, it goes to the next step but it's just blank content.


Solution

  • PrivateRoute is a layout route component, not a wrapper component, so it needs to wrap nested routes instead of children components directly. The nested routes render their element content into the Outlet that PrivateRoute renders.

    Example refactor:

    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 element={<PrivateRoute />}>
                <Route
                  path="cash-out"
                  element={(
                    <CashOut
                      activeCashOutPageTab={activeCashOutPageTab}
                      setActiveCashOutPageTab={setActiveCashOutPageTab}
                    />
                  )}
                />
                <Route path="games/:gameName" element={<GamePage />} />
              </Route>
              <Route path="terms" element={<Terms />} />
              <Route path="categories/:category" element={<Category />} />
              <Route path="auto-login" element={<AutoLogin />} />
            </Route>
          </Routes>
        </Router>
      );
    };