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

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;

问题原因与修复方案

核心问题

  1. App组件未响应状态变化:直接读取localStorage作为路由保护的判断条件,但localStorage更新时App组件不会自动重新渲染,导致路由逻辑未同步。
  2. 跳转逻辑顺序错误:Signup组件先跳转再设置localStorage,可能导致跳转时ProtectedRoutes仍读取旧状态。
  3. 冗余判断与状态管理缺失: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 04:32:09