繁体   English   中英

如何在组件呈现反应之前加载firebase数据?

[英]How to load firebase data before the component renders in react?

我有以下文件...

使用AuthStatus.js

import {useEffect, useState, useRef} from 'react';
import { getAuth, onAuthStateChanged } from 'firebase/auth';

const useAuthStatus = () => {
  const [loggedIn, setLoggedIn] = useState(false);
  const [checkingStatus, setCheckingStatus] = useState(true);
  const isMounted = useRef(true);

  
  useEffect(() => {
    if (isMounted) {
      const auth = getAuth();

      onAuthStateChanged(auth, (user) => {
        if (user) {
          setLoggedIn(true);          
        }

        setCheckingStatus(false);
      });
    }

    return () => {
      isMounted.current = false;
    }      
  }, [isMounted]);

  return {loggedIn, checkingStatus}
}

export default useAuthStatus

PrivateRoute.jsx

import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import useAuthStatus from '../hooks/useAuthStatus';
import Spinner from './Spinner';

const PrivateRoute = () => {
  const {loggedIn, checkingStatus} = useAuthStatus();

  if (checkingStatus) {
      return <Spinner/>
  }

  return loggedIn ? <Outlet /> : <Navigate to='/sign-in' />
  
}

export default PrivateRoute

配置文件.jsx

import React, {useState} from 'react';
import { db } from '../firebase.config';
import { getAuth, signOut, updateProfile } from 'firebase/auth';
import {doc, updateDoc} from 'firebase/firestore';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';

const Profile = () => {
  const auth = getAuth();

  const [changeDetails, setChangeDetails] = useState(false);

  const [formData, setFormData] = useState({
    name: auth.currentUser.displayName,
    email: auth.currentUser.email
  });

  const {name, email} = formData;

  const navigate = useNavigate();

  const onLogOut = async () => {
    await signOut(auth);

    navigate('/');
  }

  const onSubmit = async () => {
    try {
      if (auth.currentUser.displayName !== name) {
        //update display name in firebase auth
        await updateProfile(auth.currentUser, {
          displayName: name
        });

        //update name in firestore
        const userRef = doc(db, 'users', auth.currentUser.uid);

        await updateDoc(userRef, {
          name: name
        })
      }
    } catch (error) {
      toast.error('Unable to change profile details');
    }
  }

  const onChange = (e) => {
    setFormData((prevState) => (
      {
        ...prevState,
        [e.target.id]: e.target.value
      }
    ))
  }


  return (
    <div className='profile'>
      <header className='profileHeader'>
        <p className='pageHeader'>My Profile</p>
        <button type='button' className='logOut' onClick={onLogOut}>Logout</button>
      </header>

      <main>
        <div className='profileDetailsHeader'>
          <p className='profileDetailsText'>Personal Details</p>
          <p className='changePersonalDetails' onClick={() => {
            changeDetails && onSubmit();
            setChangeDetails((prevState) => !prevState);
          }}>
            {changeDetails ? 'done' : 'change'}
          </p>
        </div>

        <div className='profileCard'>
          <form>
            <input type="text" id='name' className={!changeDetails ? 'profileName' : 'profileNameActive'} disabled={!changeDetails} value={name} onChange={onChange}/>

            <input type="text" id='email' className={!changeDetails ? 'profileEmail' : 'profileEmailActive'} disabled={!changeDetails} value={email} onChange={onChange}/>
          </form>
        </div>
      </main>
    </div>
  )
}

export default Profile

应用程序.jsx

import React from "react";
import {BrowserRouter, Routes, Route} from 'react-router-dom';
import { ToastContainer } from "react-toastify";
import 'react-toastify/dist/ReactToastify.css';

import Navbar from "./components/Navbar";
import PrivateRoute from "./components/PrivateRoute";

import Explore from './pages/Explore';
import ForgotPassword from './pages/ForgotPassword';
import Offers from './pages/Offers';
import Profile from './pages/Profile';
import SignIn from './pages/SignIn';
import SignUp from './pages/SignUp';

function App() {
  return (
    <>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Explore />}/>
          <Route path="/offers" element={<Offers />}/>
          
          <Route path="/profile" element={<PrivateRoute />}>
            <Route path="/profile" element={<Profile />} />
          </Route>
          
          <Route path="/sign-in" element={<SignIn />}/>
          <Route path="/sign-up" element={<SignUp />}/>
          <Route path="/forgot-password" element={<ForgotPassword />}/>
        </Routes>

        <Navbar />
      </BrowserRouter>

      <ToastContainer position="top-center" hideProgressBar={true} autoClose={3000} pauseOnHover={false}/>
    </>
  );
}

export default App;

这是当前的工作代码......在继续我的问题之前,我将简要解释发生了什么。 当未经授权的用户访问“/profile”时,他们会被定向到 PrivateRoute 组件。 如果用户已登录,则渲染来自 react 路由器的<Outlet/>组件,然后渲染 Profile 组件。 但是,如果用户没有登录,那么他们会被 PrivateRoute 重定向到“/sign-in”。 另请注意 App.jsx 中的嵌套路由。

如果我从嵌套路由中删除 App.jsx 中的<Route path="/profile" element={<Profile />} />行并使其成为正常路由,那么当 Profile 组件加载时,我会收到错误“TypeError:无法读取 null 的属性”。 我相信我收到此错误是因为组件在const auth = getAuth();之前加载 (在 Profile.jsx 中)已完成获取数据并在 useState() 中填充姓名和电子邮件。

现在我的问题是,在 useAuthStatus.js 中,我使用 getAuth() 来获取数据,然后我再次使用 getAuth() 来获取 Profile.jsx 中的数据。 那么为什么嵌套路由(原始)代码有效,而不是这个修改后的版本? 如果我需要在 Profile.jsx 中再次使用 getAuth() ,那么数据如何在组件之前加载? 在嵌套路由中,如果外部“/profile”使用 getAuth() 那么数据是否也会以某种方式传输到嵌套路由?

好的,我想我已经明白你现在在问什么了。

现在我的问题是,在 useAuthStatus.js 中,我使用getAuth()来获取数据,然后我再次使用getAuth()来获取Profile.jsx. 那么为什么嵌套路由(原始)代码有效,而不是这个修改后的版本?

带有受保护路由组件的代码的原始版本似乎有几个原因:

  1. PrivateRoute组件不直接访问Auth对象。 它使用useAuthStatus钩子,它本身也不直接直接访问Auth对象。 useAuthStatus钩子使用onAuthStateChanged函数来“监听”身份验证状态的变化。
  2. 在 auth 状态更改(用户已登录或已注销)之前, checkingStatus状态状态会阻止呈现Profile组件。 实际上,您的代码中存在错误,当用户注销时不会更新已loggedIn状态。
  3. 当用户访问"/profile"路由并登录时,Firebase Auth对象已经缓存了用户。

直接访问和呈现Profile的更改版本似乎失败了,因为Auth对象上没有当前用户值,正如错误所指出的那样。

Uncaught TypeError: Cannot read properties of null (reading 'displayName')

轮廓

const Profile = () => {
  const auth = getAuth();

  const [changeDetails, setChangeDetails] = useState(false);

  const [formData, setFormData] = useState({
    name: auth.currentUser.displayName, // auth.currentUser is null!
    email: auth.currentUser.email
  });

  ...

所有 firebase 代码似乎都是同步的:

获取授权

返回与提供的 FirebaseApp 关联的 Auth 实例。 如果不存在实例,则使用特定于平台的默认依赖项初始化 Auth 实例。

 export declare function getAuth(app?: FirebaseApp): Auth;

验证当前用户

当前登录的用户(或 null)。

 readonly currentUser: User | null;

Auth.currentUser对象要么是经过身份验证的用户对象,要么为 null。 Profile组件试图在组件安装之前访问此currentUser属性,以设置初始渲染的初始状态值。

可以Auth.currentUser属性上使用 null-check/guard-clause 或 Optional Chaining Operator,并结合 Nullish Coalescing Operator 来提供回退值:

const Profile = () => {
  const auth = getAuth();

  const [changeDetails, setChangeDetails] = useState(false);

  const [formData, setFormData] = useState({
    name: auth.currentUser?.displayName ?? "", // displayName or ""
    email: auth.currentUser?.email ?? ""       // email or ""
  });

  ...

但这仅在组件安装时设置值,并且仅在存在经过身份验证的用户时设置。 最好坚持使用onAuthStateChanged方法来处理身份验证状态。

现在关于loggedIn错误:

const useAuthStatus = () => {
  const [loggedIn, setLoggedIn] = useState(false);
  const [checkingStatus, setCheckingStatus] = useState(true);

  useEffect(() => {
    let isMounted = true; // <-- use local isMounted variable
    
    const auth = getAuth();

    onAuthStateChanged(auth, (user) => {
      if (isMounted) { // <-- check if still mounted in callback
        setLoggedIn(!!user); // <-- coerce User | null to boolean
        setCheckingStatus(false);
      }
    });

    return () => {
      isMounted = false;
    }      
  }, []);

  return { loggedIn, checkingStatus };
};

如果我需要在Profile.jsx中再次使用getAuth() ,那么数据如何在组件之前加载?

您需要在需要访问Auth对象的任何时候使用getAuth

在嵌套路由中,如果外部“/profile”使用getAuth()那么数据是否也会以某种方式传输到嵌套路由?

并不真地。 而是您的应用有一个 Firebase 实例,该实例有一个可访问的Auth对象。 这样,它更像是一个全局上下文。 Firebase 会缓存大量数据以处理间歇性离线功能。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM