javascript - 在我的加载状态上设置一个 setTimeout 以防止登录页面在用户完成身份验证之前闪烁是不好的做法吗?
问题描述
我正在开发一个 React Native 应用程序,在用户通过身份验证之前,我能够让登录页面停止闪烁的唯一方法是添加一个 setTimeout,如下所示:
export default function App() {
const [IsReady, setIsReady] = useState(false);
const LoadFonts = async () => {
await useFonts();
};
if (!IsReady) {
return (
<AppLoading
startAsync={LoadFonts}
onFinish={() =>
setTimeout(() => {
setIsReady(true);
}, 1000)
}
onError={(error) => {
console.log(error);
}}
/>
);
}
return <Providers />;
这是不好的做法吗?有什么更好的方法来解决这个问题?我必须这样做的原因是,在我的 Routes.js 文件中,我检查用户是否经过身份验证,如果没有,他们将获得登录堆栈导航。如果是,他们将获得主页。
路由.js
export default function Routes() {
const { user, setUser, setFirestoreUserData } = useContext(AuthUserContext);
useEffect(() => {
const unsubscribeAuth = auth.onAuthStateChanged(async (authUser) => {
await (authUser ? setUser(authUser) : setUser(null));
});
return unsubscribeAuth;
}, []);
useEffect(() => {
const fetchData = async () => {
const getUserObject = await getUserFromFirestore(user.uid);
setFirestoreUserData(getUserObject);
};
if (user) {
fetchData();
}
}, [user]);
return (
<NavigationContainer theme={navigationTheme}>
{user ? <TabStack /> : <AuthStack />}
</NavigationContainer>
);
}
如果有人对如何重构有任何建议,非常感谢!谢谢你。
解决方案
您遇到的闪烁是由您的Routes
组件引起的。
这是因为当用户首次加载您的站点时,他们的身份验证状态为pending。在 SDK 表明用户已正确登录之前,它必须首先打电话到 Firebase 身份验证服务器以检查该用户会话是否有效。如果您firebase.auth().currentUser
在此过程中调用,它将返回null
。
因此,因为您的Routes
组件包含用户来自的这些行firebase.auth().currentUser
:
return (
<NavigationContainer theme={navigationTheme}>
{user ? <TabStack /> : <AuthStack />}
</NavigationContainer>
);
您的页面会在is期间呈现AuthStack
片刻,然后在确认用户会话后重新呈现。user
null
TabStack
无论您在其中使用什么值,这都会始终发生setTimeout
,App
因为它与该值无关,而是在您的Providers
组件下。
要更正此问题,您必须在确认身份验证会话时null
从您的组件返回。Routes
由于我不熟悉您的AuthUserContext
实现,因此我将根据这个来回答(它同时处理用户的状态和他们的主要用户数据):
import auth from '...'; // import from appropriate libraries
import firestore from '...';
export const FirebaseAuthUserContext = React.createContext({
// the current user's data
data: undefined,
// more information about the current user's data
dataInfo: { status: "loading" },
// the status of fetching the current user's data
dataStatus: "loading",
// the status of checking the user's auth session
initializing: true,
// the current user object
user: undefined
});
export function FirebaseAuthUserProvider({children}) {
// If authentication state has been determined already,
// use the current user object. Otherwise, fall back to `undefined`.
const [user, setUser] = useState(() => auth().currentUser || undefined);
// If initial `user` value is `undefined`, we need to initialize.
const [initializing, setInitializing] = useState(user === undefined);
// Prep a space to hold their user data
// - data?: user data object, as applicable
// - error?: error related to the user's data, as applicable
// - ref?: reference to user data's location, as applicable
// - status: status of the user data
const [userDataInfo, setUserDataInfo] = useState({ status: "loading" });
function onAuthStateChanged(user) {
setUser(user); // user is firebase.auth.User | null
if (initializing) setInitializing(false);
}
useEffect(() => auth().onAuthStateChanged(onAuthStateChanged), []);
useEffect(() => {
if (initializing) return; // do nothing, still loading auth state.
if (user === null) {
setUserDataInfo({ status: 'signed-out', data: null });
return;
}
const userDataRef = firestore()
.collection("users")
.doc(user.uid);
return userDataRef
.onSnapshot({
next: (snapshot) => {
if (snapshot.exists) {
setUserDataInfo({
status: "loaded",
get data() { return snapshot.data() },
ref: userDataRef
});
} else {
setUserDataInfo({
status: "not-found",
data: null,
ref: userDataRef
})
}
});,
error: (error) => setUserDataInfo({
status: 'error',
data: null,
error
})
});
}, [user]);
// you can rename these as desired:
return (
<FirebaseAuthUserContext.Provider value={{
data: userDataInfo.data, // for convenience
dataInfo: userDataInfo,
dataStatus: userDataInfo.status, // for convenience
initializing,
user
}}>
{children}
</FirebaseAuthUserContext.Provider>
);
}
注意:您可以将用户数据从这里FirebaseAuthUserContext
拆分到另一个上下文中,例如FirebaseAuthUserDataContext
,但通常情况下,无论如何您都需要两者,因此您可以将它们放在一起。
使用上面的Context
对象,您可以将Routes
组件更新为:
import FirebaseAuthUserContext from '...';
export default function Routes() {
const userInfo = useContext(FirebaseAuthUserContext);
if (userInfo.initializing) return null; // hide while loading
return (
<NavigationContainer theme={navigationTheme}>
{userInfo.user ? <TabStack /> : <AuthStack />}
</NavigationContainer>
);
}
这是另一个使用此Context
对象的示例,该对象需要使用用户数据:
import FirebaseAuthUserContext from '...';
export default function CurrentUserIcon() {
const userInfo = useContext(FirebaseAuthUserContext);
switch (userInfo.dataStatus) {
case "loaded":
return (
<div class="user-icon">
<a href="/profile/settings">
<img src={userInfo.data.profileImage} />
<span>@{userInfo.data.username}</span>
</a>
</div>
);
case "not-found":
return (
<div class="user-icon">
<a href="/profile/settings">
<img src={PLACEHOLDER_IMAGE_URL} />
<span>New user</span>
</a>
</div>
);
default:
// unexpected status: loading/error/signed-out
return null;
}
}
如果您正在使用 Typescript,您可以通过定义上下文可能处于的各种不同状态来帮助自己:
interface UserData {
// shape of your user's data
}
interface UserDataInfo$Error {
status: "error";
data: null;
error: firebase.firestore.FirestoreError;
}
interface UserDataInfo$Loaded {
status: "loaded";
data: UserData;
ref: firebase.firestore.DocumentReference<UserData>;
}
interface UserDataInfo$Loading {
status: "loading";
data?: undefined;
}
interface UserDataInfo$NotFound {
status: "not-found";
data: null;
ref: firebase.firestore.DocumentReference<UserData>;
}
interface UserDataInfo$SignedOut {
status: "signed-out";
data: null;
}
type UserDataInfo =
| UserDataInfo$Error
| UserDataInfo$Loaded
| UserDataInfo$Loading
| UserDataInfo$NotFound
| UserDataInfo$SignedOut;
interface LiftedUserDataInfo<T extends UserDataInfo> {
data: T["data"];
dataInfo: T;
dataStatus: T["status"];
}
interface FirebaseAuthUserContextType$Initializing extends LiftedUserDataInfo<UserDataInfo$Loading> {
user: undefined;
initializing: true;
}
interface FirebaseAuthUserContextType$SignedIn<T extends
| UserDataInfo$Error
| UserDataInfo$Loaded
| UserDataInfo$Loading
| UserDataInfo$NotFound
> extends LiftedUserDataInfo<T> {
user: firebase.auth.User;
initializing: false;
}
interface FirebaseAuthUserContextType$SignedOut extends LiftedUserDataInfo<UserDataInfo$SignedOut> {
user: null;
initializing: false;
}
type FirebaseAuthUserContextType =
| FirebaseAuthUserContextType$Initializing
| FirebaseAuthUserContextType$SignedIn<UserDataInfo$Error>
| FirebaseAuthUserContextType$SignedIn<UserDataInfo$Loaded>
| FirebaseAuthUserContextType$SignedIn<UserDataInfo$Loading>
| FirebaseAuthUserContextType$SignedIn<UserDataInfo$NotFound>
| FirebaseAuthUserContextType$SignedOut;
export const FirebaseAuthUserContext = React.createContext<FirebaseAuthUserContextType>({
data: undefined,
dataInfo: { status: "loading" },
dataStatus: "loading",
initializing: true,
user: undefined
} as FirebaseAuthUserContextType$Initializing);
推荐阅读
- html - 使用用户类重定向到同一页面的 Django 登录
- python-3.x - 在这种情况下如何打印
- javascript - Javascript中布尔值和数组的困难
- list - Netlogo 通过附加海龟列表中的所有项目来创建包含所有项目的列表
- angular - Angular 6、ngc、AOT、Angular 路由器、router.ngfactory 和 SystemJS
- android - 应用程序 X 尚未注册 - 根据其他答案尝试了建议
- inno-setup - Inno Setup - 根据文件版本中止编译
- asp.net-web-api - 在属性路由中包含额外段时在 ASP.NET WebAPI 应用程序中接收 403
- php - 在 PHP 中创建多个 CSV 文件
- react-native - react native从另一个页面返回时会调用哪个生命周期方法?