Next.js 13组件与页面数据共享及登录状态获取异常问题
问题:Next.js + React Context共享Firebase登录状态始终返回false
我正在用Next.js和React Hook Context实现网站账号用户状态的跨组件/页面共享,但每次访问数据时,isLoggedIn始终返回false。
相关代码
src/layout.tsx
"use client"; import "./globals.css"; import Header from "./components/Header"; import Footer from "./components/Footer"; import React, { useState, useEffect } from "react"; import { usePathname, useRouter } from "next/navigation"; import AuthProvider from "./contexts/AuthContext"; import { useIsLogged } from "./hooks/IsLogged"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { const [menu, setMenu] = useState(false); const path = usePathname(); const router = useRouter(); const { isLoggedIn } = useIsLogged(); useEffect(() => { if (isLoggedIn) { if (path === "/admin/login") { router.push("/admin/blog"); } } else { if (path.includes("/admin")) { router.push("/admin/login"); } } }, [isLoggedIn]); return ( <html lang="en"> <body className={`${menu && "overflow-hidden sm:overflow-auto"}`}> <AuthProvider> <Header menu={menu} setMenu={setMenu} /> {children} <Footer /> </AuthProvider> </body> </html> ); }
/src/contexts/AuthContext.tsx
import React, { useState, useEffect, createContext, ReactNode } from "react"; import { auth } from "@/services/firebase"; export const AuthContext = createContext( {} as { isLoggedIn: boolean; setLoggedIn: React.Dispatch<React.SetStateAction<boolean>>; }, ); export default function AuthProvider({ children }: { children: ReactNode }) { const [isLoggedIn, setLoggedIn] = useState(false); useEffect(() => { const unsubscribe = auth.onAuthStateChanged((user) => { // When Firebase Auth State Change if (user) setLoggedIn(true); }); return () => { unsubscribe(); }; }, []); return ( <AuthContext.Provider value={{ isLoggedIn, setLoggedIn }}> {children} </AuthContext.Provider> ); }
/src/hooks/IsLogged.ts
import { useContext } from "react"; import { AuthContext } from "@/app/contexts/AuthContext"; export const useIsLogged = () => { const value = useContext(AuthContext); return value; };
我使用Firebase Authentication进行用户认证。
问题原因分析
- Context Provider作用范围错误:
RootLayout中直接调用了useIsLogged,但AuthProvider只包裹了内部的Header、children和Footer,RootLayout本身不在Provider范围内,导致获取的是Context的初始默认值false,且后续状态更新无法同步。 - Firebase状态处理不完整:仅在用户登录时设置
isLoggedIn: true,未处理用户登出的情况,状态不会自动更新为false。 - 初始状态与实际状态不同步:页面加载时Firebase需要时间获取当前用户状态,初始
useState(false)可能与实际状态不符,导致初始值错误。
解决方案
1. 调整AuthProvider的包裹范围
将需要使用Context的逻辑抽离到子组件,让AuthProvider包裹整个需要访问Context的部分:
"use client"; import "./globals.css"; import Header from "./components/Header"; import Footer from "./components/Footer"; import React, { useState, useEffect } from "react"; import { usePathname, useRouter } from "next/navigation"; import AuthProvider from "./contexts/AuthContext"; import { useIsLogged } from "./hooks/IsLogged"; // 抽离需要访问Context的内容到子组件 const LayoutContent = ({ children }: { children: React.ReactNode }) => { const [menu, setMenu] = useState(false); const path = usePathname(); const router = useRouter(); const { isLoggedIn, isLoading } = useIsLogged(); useEffect(() => { if (isLoading) return; // 加载中不执行路由跳转 if (isLoggedIn) { path === "/admin/login" && router.push("/admin/blog"); } else { path.includes("/admin") && router.push("/admin/login"); } }, [isLoggedIn, isLoading, path, router]); return ( <> <Header menu={menu} setMenu={setMenu} /> {children} <Footer /> </> ); }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <AuthProvider> <LayoutContent>{children}</LayoutContent> </AuthProvider> </body> </html> ); }
2. 完善Firebase Auth状态处理
添加加载状态,同时处理用户登出的情况:
import React, { useState, useEffect, createContext, ReactNode } from "react"; import { auth } from "@/services/firebase"; export const AuthContext = createContext( {} as { isLoggedIn: boolean; isLoading: boolean; // 添加加载状态 setLoggedIn: React.Dispatch<React.SetStateAction<boolean>>; }, ); export default function AuthProvider({ children }: { children: ReactNode }) { const [isLoggedIn, setLoggedIn] = useState(false); const [isLoading, setIsLoading] = useState(true); // 初始处于加载状态 useEffect(() => { const unsubscribe = auth.onAuthStateChanged((user) => { setLoggedIn(!!user); // 有用户则为true,无用户则为false setIsLoading(false); // 获取到状态后结束加载 }); return () => unsubscribe(); }, []); // 加载中可显示加载组件,避免错误路由跳转 if (isLoading) { return <div>加载中...</div>; } return ( <AuthContext.Provider value={{ isLoggedIn, isLoading, setLoggedIn }}> {children} </AuthContext.Provider> ); }
3. 更新Hook(可选)
确保Hook返回完整的Context值(当前代码已满足,无需修改):
import { useContext } from "react"; import { AuthContext } from "@/app/contexts/AuthContext"; export const useIsLogged = () => { const value = useContext(AuthContext); return value; };
内容的提问来源于stack exchange,提问作者carlos
相关产品推荐
相关产品推荐

