React+Firebase多角色登录跳转异常:先加载错误组件再跳转
解决React+Firebase多角色登录切换时的组件加载异常问题
问题现象
集成Firebase的React应用包含Admin、user、author三种角色,首次登录某角色时跳转正常,但切换角色登录时出现异常:
- 先登录Admin后退出,再登录user,会先加载AdminDashboard组件(控制台打印对应日志),之后才跳转到user组件
- 先登录author,会依次加载Admin、user组件后才跳转到author组件
核心原因
- Dashboard组件的auth监听未清理:每个Dashboard组件的
useEffect中注册了onAuthStateChanged监听,但未添加清理函数,组件卸载后监听仍在运行。切换角色登录时,Firebase auth状态更新会触发所有旧监听,导致旧Dashboard组件代码执行。 - 登录流程时机问题:登录时先完成Firebase auth登录,再查询角色并跳转路由。此期间auth状态已变为登录,旧Dashboard组件的监听会先触发,直到角色查询完成后才跳转到正确路由。
- 缺少统一路由守卫:每个Dashboard组件单独做auth校验,未在路由层面统一拦截未授权访问,导致错误路由的组件被短暂挂载。
解决方案
1. 给Dashboard组件的auth监听添加清理函数
修改所有Dashboard组件的useEffect,在组件卸载时取消auth监听:
export default function AdminDashboard() { useEffect(() => { // 保存监听的 unsubscribe 函数 const unsubscribe = onAuthStateChanged(firebaseAuth, (currentUser) => { if (currentUser) { console.log("admin dash"); setUser(currentUser); } else { navigate("/"); } }); // 组件卸载时执行清理 return () => unsubscribe(); }, []); }
对UserDashboard、AuthorDashboard执行同样修改,确保组件卸载后不再监听auth状态变化。
2. 优化退出登录逻辑,彻底清除状态
退出登录时,不仅调用Firebase的signOut,还要重置本地角色和用户状态,强制跳转到登录页:
const handleSignOut = async () => { await signOut(firebaseAuth); // 重置本地状态 setRole(null); setUser(null); // 强制替换路由,避免回退到旧Dashboard navigate("/", { replace: true }); };
3. 统一路由守卫,拦截未授权访问
创建ProtectedRoute组件,在路由层面校验用户角色,避免错误组件挂载:
// 定义路由守卫组件 function ProtectedRoute({ requiredRole, children }) { const [user, setUser] = useState(null); const [role, setRole] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const unsubscribe = onAuthStateChanged(firebaseAuth, async (currentUser) => { if (currentUser) { setUser(currentUser); // 查询用户角色 const q = query(userCollection, where("email", "==", currentUser.email)); const querySnapshot = await getDocs(q); querySnapshot.forEach(doc => setRole(doc.data().role)); } else { navigate("/"); } setLoading(false); }); return () => unsubscribe(); }, []); if (loading) return <div>Loading...</div>; // 校验角色匹配度,不匹配则跳转到对应角色页面 if (role !== requiredRole) { switch(role) { case "admin": navigate("/admin", { replace: true }); break; case "user": navigate("/user", { replace: true }); break; case "author": navigate("/author", { replace: true }); break; default: navigate("/", { replace: true }); } return null; } return children; }
在路由配置中使用该守卫:
<Routes> <Route path="/" element={<LoginSignup />} /> <Route path="/admin" element={ <ProtectedRoute requiredRole="admin"> <AdminDashboard /> </ProtectedRoute> } /> <Route path="/user" element={ <ProtectedRoute requiredRole="user"> <UserDashboard /> </ProtectedRoute> } /> <Route path="/author" element={ <ProtectedRoute requiredRole="author"> <AuthorDashboard /> </ProtectedRoute> } /> </Routes>
4. 优化登录流程,确保角色查询完成后再跳转
修改登录组件的signIn函数,先获取角色再更新状态并跳转:
export default function LoginSignup() { const signIn = async (e) => { e.preventDefault(); if (!email || !password) return; try { const userCredential = await signInWithEmailAndPassword(firebaseAuth, email, password); const q = query(userCollection, where("email", "==", email)); const querySnapshot = await getDocs(q); if (querySnapshot.empty) { console.log("role undefined"); return; } const userDoc = querySnapshot.docs[0]; const userRole = userDoc.data().role; console.log("//////////////" + userRole); // 先设置本地状态 setRole(userRole); setUser(userCredential.user); // 根据角色跳转,使用replace避免回退到登录页 switch(userRole) { case "admin": navigate("/admin", { replace: true }); break; case "user": navigate("/user", { replace: true }); break; case "author": navigate("/author", { replace: true }); break; default: console.log("role undefined"); } } catch (error) { alert(error.message); } finally { handlerEmail(""); handlerPassword(""); } }; }
内容的提问来源于stack exchange,提问作者thanvi
相关产品推荐
相关产品推荐

