You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 21:15:11