React中Firebase受保护路由问题:管理员无法访问/home路由
问题描述
我用Firebase Authentication实现用户登录功能,将角色信息存储在Realtime Database中。管理员角色的用户登录后应该能访问/home路由,但目前访问该路由会被重定向至/。我能在控制台看到认证状态和角色信息,知道是useEffect导致数据库数据无法立即获取,但不知道该怎么解决。
App.js 代码
import "./App.css"; import React from "react"; import { Route, Routes, Navigate } from "react-router-dom"; import Landing from "./components/Landing"; import PhoneDetails from "./components/PhoneDetails"; import Home from "./components/Home/App.jsx"; import Signup from "./components/Signup"; import SignIn from "./components/Signin"; import ForgotPassword from "./components/ForgotPassword"; import { auth } from "./firebase-config.js"; import { useEffect } from "react"; import FirebaseData from "./firebaseData"; function App() { document.body.style = "background: #F8F5FA;"; // getting the user data from firebase const firebaseData = FirebaseData(); const [displayName, setDisplayName] = React.useState(""); const [isAuthenticated, setIsAuthenticated] = React.useState(false); const [role, setRole] = React.useState(""); useEffect(() => { if (firebaseData) { auth.onAuthStateChanged((user) => { if (user) { // User is signed in // ... setIsAuthenticated(true); setDisplayName(user.displayName); setRole(firebaseData.users?.[user.uid]?.role); } else { // User is signed out // ... setIsAuthenticated(false); setDisplayName(""); setRole(""); } }); } }, [firebaseData]); console.log("isAuthenticated:", isAuthenticated); console.log("displayName:", displayName); console.log("role:", role); return ( <Routes> <Route path="/" exact element={ <Home isAuthenticated={isAuthenticated} displayName={displayName} role={role} /> } /> <Route path="/signup" element={<Signup />} /> <Route path="/signin" element={<SignIn />} /> <Route path="/forgot-password" element={<ForgotPassword />} /> <Route path="/home" element={ isAuthenticated && role === "admin" ? ( <Landing /> ) : ( <Navigate replace to="/" /> ) } /> <Route path="/details" element={ isAuthenticated && role === "admin" ? ( <PhoneDetails /> ) : ( <Navigate replace to="/" /> ) } /> <Route path="/" element={<Navigate replace to="/" />} /> </Routes> ); } export default App;
firebaseData.js 代码
import {database} from "./firebase-config"; import React from "react"; import {ref, onValue} from "firebase/database"; import {useEffect} from "react"; const db = database; export default function FirebaseData() { const [data, setData] = React.useState(null); useEffect(() => { onValue(ref(db), (snapshot) => { setData(snapshot.val()); }); }, []); return data; }
解决方案
问题核心是初始加载时数据库数据还未获取完成,role为空值,导致路由守卫直接判定权限不通过并执行重定向。可以通过以下几种方式解决:
1. 增加加载状态,避免提前判定
在App组件中新增isLoading状态,标记用户信息和数据库数据是否加载完成。加载过程中显示占位内容(比如加载动画),完成后再进行路由权限判断:
修改App.js的状态和逻辑:
function App() { document.body.style = "background: #F8F5FA;"; const firebaseData = FirebaseData(); const [displayName, setDisplayName] = React.useState(""); const [isAuthenticated, setIsAuthenticated] = React.useState(false); const [role, setRole] = React.useState(""); // 新增加载状态 const [isLoading, setIsLoading] = React.useState(true); useEffect(() => { const unsubscribe = auth.onAuthStateChanged((user) => { if (user) { setIsAuthenticated(true); setDisplayName(user.displayName); // 数据库数据存在时再设置角色 if (firebaseData) { setRole(firebaseData.users?.[user.uid]?.role); setIsLoading(false); } } else { setIsAuthenticated(false); setDisplayName(""); setRole(""); setIsLoading(false); } }); // 监听数据库数据变化,数据加载完成后更新角色并结束加载 if (firebaseData && isAuthenticated) { const currentUser = auth.currentUser; if (currentUser) { setRole(firebaseData.users?.[currentUser.uid]?.role); setIsLoading(false); } } // 清理auth监听订阅 return unsubscribe; }, [firebaseData, isAuthenticated]); // 加载中时显示占位内容 if (isLoading) { return <div>加载中...</div>; // 可替换为自定义加载组件 } return ( <Routes> <Route path="/" exact element={ <Home isAuthenticated={isAuthenticated} displayName={displayName} role={role} /> } /> <Route path="/signup" element={<Signup />} /> <Route path="/signin" element={<SignIn />} /> <Route path="/forgot-password" element={<ForgotPassword />} /> <Route path="/home" element={ isAuthenticated && role === "admin" ? ( <Landing /> ) : ( <Navigate replace to="/" /> ) } /> <Route path="/details" element={ isAuthenticated && role === "admin" ? ( <PhoneDetails /> ) : ( <Navigate replace to="/" /> ) } /> {/* 修复冗余路由,匹配所有未定义路径 */} <Route path="*" element={<Navigate replace to="/" />} /> </Routes> ); }
2. 优化数据库监听范围,减少加载延迟
当前FirebaseData组件监听整个数据库,数据量大会导致加载变慢。可以修改为只监听当前登录用户的角色节点,减少数据传输:
修改firebaseData.js为带参数的自定义Hook:
import {database} from "./firebase-config"; import React from "react"; import {ref, onValue} from "firebase/database"; import {useEffect} from "react"; const db = database; // 接收用户uid,只监听该用户的角色数据 export default function useUserRole(uid) { const [role, setRole] = React.useState(null); const [isLoading, setIsLoading] = React.useState(true); useEffect(() => { if (!uid) { setRole(null); setIsLoading(false); return; } // 仅监听用户角色节点 const userRoleRef = ref(db, `users/${uid}/role`); const unsubscribe = onValue(userRoleRef, (snapshot) => { setRole(snapshot.val()); setIsLoading(false); }); return unsubscribe; }, [uid]); return {role, isLoading}; }
然后在App.js中使用这个Hook:
// 替换原FirebaseData导入 import useUserRole from "./firebaseData"; function App() { document.body.style = "background: #F8F5FA;"; const [displayName, setDisplayName] = React.useState(""); const [isAuthenticated, setIsAuthenticated] = React.useState(false); const [currentUid, setCurrentUid] = React.useState(null); // 使用自定义Hook获取角色和加载状态 const {role, isLoading} = useUserRole(currentUid); useEffect(() => { const unsubscribe = auth.onAuthStateChanged((user) => { if (user) { setIsAuthenticated(true); setDisplayName(user.displayName); setCurrentUid(user.uid); } else { setIsAuthenticated(false); setDisplayName(""); setCurrentUid(null); } }); return unsubscribe; }, []); if (isLoading) { return <div>加载中...</div>; } // 后续路由代码保持不变 }
通过以上修改,会先等待角色数据加载完成再进行路由判断,避免因role为空导致的错误重定向。
内容的提问来源于stack exchange,提问作者002 DMAM Jigyasa Nagpal
相关产品推荐
相关产品推荐

