React集成Firebase认证:登出后再次登录无法跳转仅刷新页面
Firebase认证跳转异常问题
我基于Firebase开发认证功能,已完成登录、登出、首页页面开发,初始运行正常。但修改路由或代码后出现以下问题:
- 首次登录可正常跳转至Home页,登出后再次登录无法跳转,仅刷新当前登录页面
isLoggedIn设为true后,Login组件会重新加载,手动修改URL至/home可正常访问
相关代码
App.js
import Signup from "./Signup"; import Login from "./Login"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import Home from "./Home"; import ProtectedRoutes from "../ProtectedRoutes"; export default function App() { return ( <BrowserRouter> <Routes> <Route path="/"> <Route path="home" element={ <ProtectedRoutes isLoggedIn={JSON.parse(localStorage.getItem("isLoggedIn"))} > <Home /> </ProtectedRoutes> } /> <Route path="login" element={<Login />} /> <Route path="signup" element={<Signup />} /> </Route> </Routes> </BrowserRouter> ); }
Login.js
import React from "react"; import { useRef, useState } from "react"; import { Button, Card, Form, Alert, Container } from "react-bootstrap"; import { signInWithEmailAndPassword } from "firebase/auth"; import { auth } from "../firebaserc"; import { Link, useNavigate } from "react-router-dom"; export default function Login() { const emailRef = useRef(); const passwordRef = useRef(); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const navigate = useNavigate(); const handleSubmit = (e) => { e.preventDefault(); signInWithEmailAndPassword( auth, emailRef.current.value, passwordRef.current.value ) .then((userCredential) => { const user = userCredential.user; console.log(user); localStorage.setItem("isLoggedIn", true); if (JSON.parse(localStorage.getItem("isLoggedIn"))) { navigate("/home"); } setError(""); }) .catch((error) => { setError(error.message); }); }; return ( <> <Container className="d-flex align-items-center justify-content-center" style={{ minHeight: "100vh", }} > <div className="w-100" style={{ maxWidth: "400px", }} > <Card> <Card.Body> <h2 className="text-center mb-3">Login</h2> {error && <Alert variant="danger">{error}</Alert>} <Form onSubmit={handleSubmit}> <Form.Group id="email" className="mb-2"> <Form.Label>Email</Form.Label> <Form.Control type="email" ref={emailRef} required /> </Form.Group> <Form.Group id="password" className="mb-2"> <Form.Label>Password</Form.Label> <Form.Control type="password" ref={passwordRef} required /> </Form.Group> <Button disabled={loading} className="w-100 mt-2" type="submit"> Login </Button> </Form> </Card.Body> </Card> <div className="w-100 text-center mt-2"> New here? <Link to="/signup">Signup</Link> </div> </div> </Container> </> ); }
Signup.js
import React from "react"; import { useRef, useState } from "react"; import { Button, Card, Form, Alert, Container } from "react-bootstrap"; import { createUserWithEmailAndPassword } from "firebase/auth"; import { auth } from "../firebaserc"; import { Link, useNavigate } from "react-router-dom"; export default function Signup() { const emailRef = useRef(); const passwordRef = useRef(); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const navigate = useNavigate(); const handleSubmit = (e) => { e.preventDefault(); createUserWithEmailAndPassword( auth, emailRef.current.value, passwordRef.current.value ) .then((userCredential) => { const user = userCredential.user; console.log(user); setError(""); navigate("/home"); localStorage.setItem("isLoggedIn", true); }) .catch((error) => { setError(error.message); }); }; return ( <> <Container className="d-flex align-items-center justify-content-center" style={{ minHeight: "100vh", }} > <div className="w-100" style={{ maxWidth: "400px", }} > <Card> <Card.Body> <h2 className="text-center mb-3"> Sign Up</h2> {error && <Alert variant="danger">{error}</Alert>} <Form onSubmit={handleSubmit}> <Form.Group id="email" className="mb-2"> <Form.Label>Email</Form.Label> <Form.Control type="email" ref={emailRef} required /> </Form.Group> <Form.Group id="password" className="mb-2"> <Form.Label>Password</Form.Label> <Form.Control type="password" ref={passwordRef} required /> </Form.Group> <Button disabled={loading} className="w-100 mt-2" type="submit"> Sign Up </Button> </Form> </Card.Body> </Card> <div className="w-100 text-center mt-2"> Already have an account <Link to="/login">Login</Link> </div> </div> </Container> </> ); }
Home.js
import React from "react"; import { Button } from "react-bootstrap"; import { auth } from "../firebaserc"; import { signOut } from "firebase/auth"; import { useNavigate } from "react-router-dom"; export default function Home() { const navigate = useNavigate(); const handleLogout = () => { signOut(auth) .then(() => { localStorage.removeItem("isLoggedIn"); navigate("/login", { replace: true }); }) .catch((error) => { console.log(error.message); }); }; return ( <div> Home <Button onClick={handleLogout}>Logout</Button> </div> ); }
ProtectedRoutes.js
import { Navigate } from "react-router-dom"; const ProtectedRoutes = ({ isLoggedIn, children }) => { if (!isLoggedIn) { return <Navigate to="/login" replace />; } return children; }; export default ProtectedRoutes;
问题原因与修复方案
核心问题
- App组件未响应状态变化:直接读取
localStorage作为路由保护的判断条件,但localStorage更新时App组件不会自动重新渲染,导致路由逻辑未同步。 - 跳转逻辑顺序错误:Signup组件先跳转再设置
localStorage,可能导致跳转时ProtectedRoutes仍读取旧状态。 - 冗余判断与状态管理缺失:Login组件中多余的
localStorage判断,且未正确管理loading状态。
修复步骤
1. 让App组件响应认证状态
使用Firebase官方的onAuthStateChanged监听用户状态,替代依赖localStorage的方式,确保状态变化时组件重新渲染:
// 修改App.js import { useState, useEffect } from "react"; import { onAuthStateChanged } from "firebase/auth"; import { auth } from "./firebaserc"; // 路径根据实际项目调整 export default function App() { const [isLoggedIn, setIsLoggedIn] = useState(false); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { setIsLoggedIn(!!user); setIsLoading(false); localStorage.setItem("isLoggedIn", !!user); }); return unsubscribe; // 组件卸载时清理监听 }, []); if (isLoading) { return <div>Loading...</div>; // 避免初始状态闪烁 } return ( <BrowserRouter> <Routes> <Route path="/"> <Route path="home" element={ <ProtectedRoutes isLoggedIn={isLoggedIn}> <Home /> </ProtectedRoutes> } /> <Route path="login" element={<Login />} /> <Route path="signup" element={<Signup />} /> </Route> </Routes> </BrowserRouter> ); }
2. 简化Login组件跳转逻辑
移除冗余的localStorage判断,登录成功后直接跳转,并正确管理loading状态:
// 修改Login.js的handleSubmit const handleSubmit = (e) => { e.preventDefault(); setLoading(true); signInWithEmailAndPassword( auth, emailRef.current.value, passwordRef.current.value ) .then(() => { setError(""); navigate("/home", { replace: true }); // 替换历史记录,避免返回登录页 }) .catch((error) => { setError(error.message); }) .finally(() => { setLoading(false); }); };
3. 修正Signup组件顺序
先同步localStorage再执行跳转,确保状态一致:
// 修改Signup.js的handleSubmit const handleSubmit = (e) => { e.preventDefault(); setLoading(true); createUserWithEmailAndPassword( auth, emailRef.current.value, passwordRef.current.value ) .then(() => { setError(""); localStorage.setItem("isLoggedIn", true); navigate("/home", { replace: true }); }) .catch((error) => { setError(error.message); }) .finally(() => { setLoading(false); }); };
4. 优化ProtectedRoutes(可选)
直接基于Firebase auth状态判断,彻底摆脱对localStorage的依赖:
// 修改ProtectedRoutes.js import { useState, useEffect } from "react"; import { Navigate } from "react-router-dom"; import { onAuthStateChanged } from "firebase/auth"; import { auth } from "../firebaserc"; const ProtectedRoutes = ({ children }) => { const [isLoggedIn, setIsLoggedIn] = useState(null); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { setIsLoggedIn(!!user); }); return unsubscribe; }, []); if (isLoggedIn === null) { return <div>Loading...</div>; } return isLoggedIn ? children : <Navigate to="/login" replace />; }; export default ProtectedRoutes;
内容的提问来源于stack exchange,提问作者Akshay Srivastava
相关产品推荐
相关产品推荐

