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

如何使用Firebase Authentication实现页面刷新时保持用户登录

解决页面刷新时无法获取Firebase用户UID的问题

问题根源

页面刷新时,Firebase Auth的onAuthStateChanged是异步执行的,初始状态下user为null,而你的页面组件直接在useEffect中访问user.uid,同时该useEffect的依赖数组没有包含user,导致user更新后不会重新触发数据获取逻辑。

具体修复步骤

1. 修正页面组件的useEffect逻辑

  • 添加user到依赖数组,确保用户状态更新时重新执行数据获取
  • 先判断user是否存在,再执行Firestore查询,避免Cannot read property 'uid' of null错误
  • 优化非管理员用户的模块获取逻辑,避免重复添加(原逻辑中每次getDoc都会追加到modules,可能导致重复数据)

修改后的页面组件代码:

import Head from 'next/head'
import Navbar from '../../components/Navbar'
import Footer from '../../components/Footer';
import Link from 'next/link';
import Module from '../../components/Module';
import { addAssessment, addModule, modalState } from '../../atoms/modalAtom';
import { useRecoilState } from 'recoil';
import MyModal from '../../components/Modal';
import { collection, doc, getDocs, onSnapshot, query } from 'firebase/firestore';
import { db } from '../../firebase';
import { useEffect, useState } from 'react';
import { useUserAuth } from '../../context/UserAuthContext';
import DashboardSidebar from '../../components/DashboardSidebar';


export default function modules() {
  const [isOpen, setIsOpen] = useRecoilState(modalState);
  const [isAddModule, setIsAddModule] = useRecoilState(addModule);
  const [isAddAssessment, setIsAddAssessment] = useRecoilState(addAssessment);
  const [modules, setModules] = useState([]);
  const {user, userInfo, setUserInfo} = useUserAuth();

  // 获取用户模块数据
  useEffect(() => {
    // 用户未登录时直接返回
    if (!user) {
      setModules([]);
      setUserInfo(null);
      return;
    }

    const unsubscribeUser = onSnapshot(
      doc(db, "users", user.uid),
      (userSnapshot) => {
        const userData = userSnapshot.data();
        setUserInfo(userData);

        if (userData.isAdmin) {
          // 管理员获取所有模块
          const unsubscribeModules = onSnapshot(
            collection(db, 'modules'),
            (snapshot) => {
              setModules(snapshot.docs);
            }
          );
          return unsubscribeModules;
        } else {
          // 普通用户获取关联模块,用Promise.all避免重复setState
          const modulePromises = userData.modules.map(moduleId => 
            getDoc(doc(db, 'modules', moduleId))
          );
          Promise.all(modulePromises).then(moduleDocs => {
            setModules(moduleDocs);
          });
        }
      }
    );

    return unsubscribeUser;
  }, [db, user]); // 添加user到依赖数组

  return (
    <div className="h-full">
      <Head>
        <title>My Uni Work</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <Navbar />
      <div className="lg:flex space-x-0 h-full">
          {/* sidebar area  */}
          <div>
          <DashboardSidebar modules />
          </div>
          

          {/* content area */}
          <div className="flex flex-col lg:px-10 mt-4 space-y-2 mb-6 w-full">
              <div className="lg:flex m-2 items-center justify-between">
                <p className="text-lg lg:text-2xl m-2 font-semibold text-[#333]">
                    Selected Modules     
                </p>
                 {userInfo?.isAdmin && (
                    <div className="flex flex-col lg:flex-row lg:items-center justify-end gap-1">
                    <button className="bg-[#103A5C] text-white font-semibold p-3 rounded-md hover:opacity-90
                        " onClick={(e) =>{
                          setIsOpen(true);
                          setIsAddModule(true);
                        }} >
                        Add Module
                    </button> 
                    <button className="bg-[#103A5C] text-white font-semibold p-3 rounded-md hover:opacity-90
                        " onClick={(e) =>{
                          setIsOpen(true);
                          setIsAddAssessment(true);
                        }} >
                        Add assessment
                    </button> 
                    
                    </div>
                 )}
              </div>
              
              {/* <h1>{modules.length}</h1> */}

              {modules.map(module =>(
                <Module dashboardPage key={module.data().moduleCode} module={module.data()} />
              ))}
            </div>
            
        </div>
        
      {/* Modal */}
      {isOpen && <MyModal/>}

      {/* <Footer /> */}
    </div>
  )
}

2. 可选:在UserAuthContext中添加加载状态(提升用户体验)

可以在Auth上下文里添加loading状态,避免页面在Auth初始化时显示错误或空白:

修改后的UserAuthContext代码:

import {createContext, useContext, useEffect, useState} from 'react';
import {
    createUserWithEmailAndPassword,
    signInWithEmailAndPassword,
    signOut,
    onAuthStateChanged,
    getAuth,
} from 'firebase/auth';

const userAuthContext = createContext();


export function UserAuthContextProvider({children}){
    const [user, setUser] = useState(null);
    const [userInfo, setUserInfo] = useState(null);
    const [loading, setLoading] = useState(true); // 添加加载状态
    const auth = getAuth();

    function signUp(email, password, isAdmin){
        return createUserWithEmailAndPassword(auth, email, password);
    }

    function signIn(email, password){
            return signInWithEmailAndPassword(auth, email, password);
    }

    function SignOut(){
        signOut(auth).then(() => {
            console.log("Sign-out successful");
            setUser(null);
            setUserInfo(null);
        }).catch((error) => {
            // 处理登出错误
        });
    }

    useEffect(() => {
        const unsubscribe = onAuthStateChanged(auth, (currentUser) =>{
            setUser(currentUser);
            setLoading(false); // Auth初始化完成,结束加载
       });
       return unsubscribe;
    }, []);

    return (
        <userAuthContext.Provider value={{user, userInfo, loading, signUp, signIn, SignOut, setUserInfo}}>
            {/* 加载时可以显示加载组件,避免页面闪烁 */}
            {loading ? <div>Loading...</div> : children}
        </userAuthContext.Provider>
    )
}

export function useUserAuth() {
    return useContext(userAuthContext);
}

然后在页面组件中可以使用loading状态,比如:

const {user, userInfo, loading, setUserInfo} = useUserAuth();

if (loading) {
  return <div>Loading...</div>;
}

关键说明

  • 依赖数组必须包含user,确保用户状态变化时重新获取数据
  • 必须先判断user是否存在,再访问user.uid,避免空指针错误
  • 普通用户获取模块时用Promise.all批量获取,避免多次setState导致的重复数据问题

内容的提问来源于stack exchange,提问作者Shmurds

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 02:15:37