我正在使用 React 并更新了我的应用程序以使用整体布局。
有公共路线和保护路线。
当我将代码从使用子项更改为
<Outlet>
时,当我导航到任何受保护的路线时,我会看到一个黑色页面。
我已经调试了一会儿,但我无法弄清楚。
它将如何“加载受保护的路线”......非常快,然后变黑。
有什么想法吗?
我的理解是我需要使用
<Outlet>
而不是 children
我什至尝试只返回
return <Outlet/>;
,但它仍然显示黑色。
这是我的设置:
import Admin from "./restricted/Admin";
import { AuthProvider, Logout, APIProvider, ToastProvider, ErrorPage, ErrorBoundaryRouter, ProtectedRoute } from "@app/Shared"
import { Route, Routes } from "react-router-dom";
import HomePage from "./home/Home";
import Layout from "./layouts/Layout";
function App() {
return (
<ErrorBoundaryRouter fallbackUrl="/ErrorPage">
<AuthProvider>
<APIProvider>
<ToastProvider>
<Routes>
{/* All routes share the Layout */}
<Route element={<Layout />}>
{/* Public Routes */}
<Route path="/" element={<HomePage />} />
<Route path="/public" element={<Public/>} />
{/* Error Route */}
<Route path="/Error/:message" element={<ErrorPage />} />
{/* Protected Routes */}
<Route element={<ProtectedRoute/>}>
<Route path="/admin" element={<Admin />} />
<Route path="/userUpdate" element={<userUpdate />} />
<Route path="/logout" element={<Logout />} />
</Route>
</Route>
</Routes>
</ToastProvider>
</APIProvider>
</AuthProvider>
</ErrorBoundaryRouter>
);
}
export default App;
这是受保护的路线:
import { useEffect, useState } from "react";
import { useAuth } from "./useAuth";
import { userServer } from "../api/userServer";
import { Outlet } from "react-router-dom";
export const ProtectedRoute = () => {
const { user, fetchUserClaims } = useAuth();
const userAPI = userServer();
const [loading, setLoading] = useState(true);
useEffect(() => {
const checkAuthentication = async () => {
try {
const isLoggedIn = await userAPI.getUserLoggedInStatus();
if (!isLoggedIn) {
window.location.href = "/"; // Redirect to home if not logged in
} else if (!user) {
await fetchUserClaims();
}
} catch (error) {
console.error("Error during authentication check:", error);
window.location.href = "/"; // Redirect to home on error
} finally {
setLoading(false);
}
};
checkAuthentication();
}, [user]);
if (loading) {
return <div>Loading Protected Content...</div>;
}
return user && user.isLoggedIn ? <Outlet/>: null;
};
export default ProtectedRoute;
这是我的布局:
import React, { useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { TopToolbar, useAuth, isAuthenticated } from "@app/shared";
import { CardItem, getCardItems } from "../home/cardItems";
const Layout: React.FC = () => {
const { logout, fetchUserClaims } = useAuth();
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const [userData, setUserData] = useState<{ firstName: string; lastName: string } | null>(null);
const [initials, setInitials] = useState<string>("?");
const [cardItems, setCardItems] = useState<CardItem[]>([]);
// Helper function to calculate initials
const calculateInitials = (firstName?: string, lastName?: string) => {
if (firstName && lastName) {
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
}
return "?";
};
// Fetch user data and determine card items
const checkAuthentication = async () => {
try {
const loggedInStatus = await isAuthenticated();
setIsLoggedIn(loggedInStatus);
if (loggedInStatus) {
const claims = await fetchUserClaims();
if (claims) {
setUserData({ firstName: claims.firstName, lastName: claims.lastName });
setInitials(calculateInitials(claims.firstName, claims.lastName));
setCardItems(getCardItems(true)); // Get logged-in card items
}
} else {
setUserData(null);
setInitials("?");
setCardItems(getCardItems(false)); // Get guest card items
}
} catch (error) {
setIsLoggedIn(false);
setUserData(null);
setInitials("?");
setCardItems(getCardItems(false)); // Fallback for unauthenticated state
}
};
useEffect(() => {
checkAuthentication();
}, []);
const handleLogout = () => {
logout();
setIsLoggedIn(false);
setUserData(null);
setInitials("?");
setCardItems(getCardItems(false));
};
return (
<div className="h-screen flex flex-col">
{/* Top Toolbar */}
<div>
<TopToolbar
userInitials={initials}
firstName={userData?.firstName || "Guest"}
lastName={userData?.lastName || ""}
onLogout={isLoggedIn ? handleLogout : () => {}}
allowToggleMenu={isLoggedIn}
/>
</div>
{/* Main Content */}
<main className="flex-grow overflow-auto">
{/* Provide cardItems via Outlet context */}
<Outlet context={{ cardItems }} />
</main>
</div>
);
};
export default Layout;
主要内容如下:
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { BrowserRouter } from 'react-router-dom';
import { registerLicense } from '@syncfusion/ej2-base'
registerLicense(window._env_.VITE_SYNCFUSION_LICENSE_KEY)
ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<App />
</BrowserRouter>
)
使用应用程序package.json
"dependencies": {
"@syncfusion/ej2": "~27.2.2",
"@syncfusion/ej2-base": "~27.2.2",
"@syncfusion/ej2-data": "~27.2.2",
"@syncfusion/ej2-react-buttons": "~27.2.2",
"@syncfusion/ej2-react-dropdowns": "~26.1.38",
"@syncfusion/ej2-react-grids": "~27.2.2",
"@syncfusion/ej2-react-inputs": "~27.2.2",
"@syncfusion/ej2-react-layouts": "~27.2.2",
"@syncfusion/ej2-react-navigations": "~27.2.2",
"@syncfusion/ej2-react-notifications": "~27.2.2",
"@syncfusion/ej2-react-pdfviewer": "~27.2.3",
"@syncfusion/ej2-react-popups": "~27.2.2",
"@syncfusion/ej2-react-querybuilder": "~27.2.2",
"dotenv": "~16.4.5",
"immer": "~10.0.4",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"react-router-dom": "~7.0.1",
"remark-rehype": "^11.1.1",
"use-immer": "~0.9.0",
"uuid": "~9.0.1",
"zustand": "~4.5.2"
},
"devDependencies": {
"@types/node": "~20.12.7",
"@types/react": "~18.2.79",
"@types/react-dom": "~18.2.25",
"@types/uuid": "~9.0.8",
"@typescript-eslint/eslint-plugin": "~7.7.1",
"@typescript-eslint/parser": "~7.7.1",
"@vitejs/plugin-basic-ssl": "~1.1.0",
"@vitejs/plugin-react": "~4.3.3",
"@vitejs/plugin-react-swc": "~3.6.0",
"autoprefixer": "~10.4.19",
"concurrently": "~8.2.2",
"eslint": "~8.57.0",
"eslint-plugin-react-hooks": "~4.6.0",
"eslint-plugin-react-refresh": "~0.4.6",
"postcss": "~8.4.38",
"tailwindcss": "~3.4.3",
"typescript": "~5.4.5",
"vite": "~5.2.10"
}
共享库依赖项
"dependencies": {
"@syncfusion/ej2-popups": "~27.2.2",
"@syncfusion/ej2-react-buttons": "~27.2.2",
"@syncfusion/ej2-react-dropdowns": "~27.2.2",
"@syncfusion/ej2-react-notifications": "27.2.2",
"@syncfusion/ej2-react-popups": "~27.2.2",
"dompurify": "^3.2.1",
"dotenv": "^16.4.5",
"react-router-dom": "^7.0.1",
"rehype-stringify": "^10.0.1",
"remark": "^15.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"unified": "^11.0.5",
"uuid": "~9.0.1"
},
"devDependencies": {
"@types/node": "^22.9.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-router-dom": "^5.3.3",
"@types/uuid": "~9.0.8",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.19",
"chokidar-cli": "^3.0.0",
"cpx": "^1.5.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.38",
"rimraf": "^6.0.1",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"vite": "^5.4.11"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "~7.0.1",
"tailwindcss": "^3.4.3"
}
如果加载状态未按预期解决或用户未经过正确身份验证,则可能会出现您面临的问题(黑屏)。
所以尝试更新您的代码,如下所示:
import { useEffect, useState } from "react";
import { useAuth } from "./useAuth";
import { userServer } from "../api/userServer";
import { Outlet, Navigate } from "react-router-dom";
export const ProtectedRoute = () => {
const { user, fetchUserClaims } = useAuth();
const userAPI = userServer();
const [loading, setLoading] = useState(true);
useEffect(() => {
const checkAuthentication = async () => {
try {
const isLoggedIn = await userAPI.getUserLoggedInStatus();
if (!isLoggedIn) {
setLoading(false); // Stop loading and redirect
} else if (!user) {
await fetchUserClaims();
} else {
setLoading(false); // User data is loaded, stop loading
}
} catch (error) {
console.error("Error during authentication check:", error);
setLoading(false); // Stop loading even on error
}
};
checkAuthentication();
}, [user, fetchUserClaims, userAPI]);
if (loading) {
return <div>Loading Protected Content...</div>; // Loading state
}
if (!user || !user.isLoggedIn) {
return <Navigate to="/" />; // Redirect to home if not authenticated
}
return <Outlet />; // Render protected content if authenticated
};
export default ProtectedRoute;
解释:我们现在不再使用 window.location.href,而是使用从 React-router-dom 导航来在未经身份验证或出错时重定向用户。这确保了干净的重定向,无需重新加载页面,并与 React Router 无缝协作。
只有在用户身份验证检查完成后,加载状态才会设置为 false。这可以防止在准备好之前渲染 Outlet。
如果身份验证检查期间出现错误,我们将确保更新加载状态,并相应地停止进一步渲染或重定向。
如果用户未通过身份验证或 user.isLoggedIn 为 false,则用户将被重定向到主页。否则,它会呈现 Outlet(受保护的路线)。