如何使用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
相关产品推荐
相关产品推荐

