登录页面后反应路由器 v6 不会切换到主页:警告:无法对未安装的组件执行 React 状态更新

Posted

技术标签:

【中文标题】登录页面后反应路由器 v6 不会切换到主页:警告:无法对未安装的组件执行 React 状态更新【英文标题】:react router v6 won't switch to Home Page after Login Page: Warning: Can't perform a React state update on an unmounted component 【发布时间】:2022-01-05 10:32:19 【问题描述】:

注意:使用 firebase v9

在我的 webapp 上,我试图让它在成功登录后将用户重定向到主页,如果不成功,我们会留在登录页面。

我最初只是尝试使用私有路由器,但我相信 currentUser 没有被 useAuth(); 设置;所以它没有被定义。

然后我尝试使用异步等待方法,但我得到了更多的错误。

这是我在控制台日志中得到的错误:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    at RequireAuth (http://localhost:3000/static/js/main.chunk.js:262:82)

这是我在 App.js 中的实现:

import  useState  from "react";
import "./App.css";
import Signup from "./pages/Signup";
import Login from "./pages/Login";
import  BrowserRouter, Routes, Route, Link, Navigate, Outlet  from "react-router-dom";
import Home from "./pages/Home";
import  useAuth  from "./firebase";

function App() 
  const [mainSection, setMainSection] = useState("home");

  const d = new Date();
  const time = d.getHours();
  if (time >= 18 || time < 5) 
    document.documentElement.classList.add("dark");
  

  // async function PrivateRoute(children) 
    
  //   // setLoading(true);
  //   try
  //     const currentUser = await useAuth();
  //     return currentUser ? children : <Navigate to="/login" />;
  //     // await login(emailRef.current.value, passwordRef.current.value);
  //    catch 
  //     alert("Error! Authentication failed!!")
  //   
  //   // setLoading(false);
  // 

  function RequireAuth() 
    const currentUser = useAuth();
    console.log('currentUser (private router): ', currentUser);
    if (!currentUser)  
          return <Navigate to="/login"/>;
      
    return <Outlet />
    // <Home mainSection=mainSection setMainSection=setMainSection />;
    // return auth ? children : <Navigate to="/signup" />;
  
  

  return (
      <BrowserRouter>
      <Routes>
        <Route element=<RequireAuth/>>
          <Route path="/" element=<Home mainSection=mainSection setMainSection=setMainSection />/>
        </Route>
        <Route path="/login" element=<Login /> />
        <Route path="/signup" element=<Signup /> />
      </Routes>
    </BrowserRouter>
  );


export default App;

这是我持有 useAuth 的实现:

// Import the functions you need from the SDKs you need
import  useEffect, useState  from "react";
import  initializeApp  from "firebase/app";
import  getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, onAuthStateChanged  from "firebase/auth";
import  getFirestore  from "firebase/firestore";
import  doc, setDoc  from "firebase/firestore";
import  v4 as uuidv4  from "uuid";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
const firebaseConfig = 
  apiKey: "AIzaSyBN30k6RivLOuz7KToi_uD8V5s5cmyD9RM",
  authDomain: "auth-development-62c42.firebaseapp.com",
  projectId: "auth-development-62c42",
  storageBucket: "auth-development-62c42.appspot.com",
  messagingSenderId: "414005826367",
  appId: "1:414005826367:web:7b987851735426ebedf98a"
;

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth();

export function signup(email, password) 
  return createUserWithEmailAndPassword(auth, email, password);


export function login(email, password) 
  return signInWithEmailAndPassword(auth, email, password);


// eventually write a logout function

export async function sendHabitToFirestore(uidPath, habitName) 
  const db = getFirestore();
  const habitId = uuidv4();
  const pathDocRef = doc(db, "users", uidPath, "user_habits", habitId);
  
  //initialize habitValues for the whole year
  const habitValues = []
  var d = new Date();
  var year = d.getFullYear();
  for (let day = 1; day <= 31; day++) 
    var dateString = year.toString() + '-01-' + day.toString(); 
    var value =  date: dateString, completed: false ;
    habitValues.push(value);
  

  await setDoc(pathDocRef, 
    name: habitName, 
    id: habitId,
    calendarData: habitValues,
  );


export function useAuth() 
  const [currentUser, setCurrentUser ] = useState();
  useEffect(() => 
    const unsub = onAuthStateChanged(auth, user => setCurrentUser(user));
    return unsub;
  , [])

  return currentUser;


export default getFirestore();

【问题讨论】:

【参考方案1】:

问题

所以我收集到用户在“/login”路径上并进行身份验证,然后您将他们重定向到主路径"/"。如果我正确阅读了代码,那么此时RequireAuth 包装器会挂载并检查当前用户。初始的currentUser 状态是未定义的,或者换句话说是一个虚假值,并且&lt;Navigate to="/login"/&gt; 被渲染并且用户被重定向返回 到登录页面。这卸载RequireAuth,但可能存在延迟入队状态更新。

解决方案

RequireAuth 移到外面 App。我认为这不是您问题的直接原因,但在其他 React 组件中声明 React 组件只是一种反模式。每次App 重新呈现时,您实际上 都在声明一个 RequireAuth 组件,并且任何组件状态都将丢失。

要解决路由问题,请添加一个已验证或未验证的不确定“待处理”状态,并且在包装器组件确认 currentUser 身份验证状态之前,不要承诺呈现出口或重定向。使用 undefined 可以解决此问题,因为它既不是用户对象也不是 null 且没有用户。

function RequireAuth() 
  const currentUser = useAuth();

  if (currentUser === undefined) 
    return null; // or loading indicator, etc...
  

  return currentUser ? <Outlet /> : <Navigate to="/login"/>;

【讨论】:

以上是关于登录页面后反应路由器 v6 不会切换到主页:警告:无法对未安装的组件执行 React 状态更新的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式重定向到反应路由器 v6 中的路由的问题

以编程方式重定向到反应路由器 v6 中的路由的问题

使用反应路由器 v6 的反应嵌入式登录实现

登录页面不会重定向到路由到登录页面

条件满足后,私有路由不重定向

使用反应路由器 V6 和刷新页面时我得到空白页面