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.
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>
);
};