[英]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.
那么为什么嵌套路由(原始)代码有效,而不是这个修改后的版本?
带有受保护路由组件的代码的原始版本似乎有几个原因:
PrivateRoute
组件不直接访问Auth
对象。 它使用useAuthStatus
钩子,它本身也不直接直接访问Auth
对象。 useAuthStatus
钩子使用onAuthStateChanged
函数来“监听”身份验证状态的变化。checkingStatus
状态状态会阻止呈现Profile
组件。 实际上,您的代码中存在错误,当用户注销时不会更新已loggedIn
状态。"/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.