我尝试更新登录用户的个人资料照片,我使用 Asyncstorage 登录会话,并使用 APP.js 中的状态重置它,但在另一个选项卡中,我使用 Asyncstorage 登录会话,但是当更新个人资料照片时出现此错误可能未处理的承诺拒绝(id:4): 类型错误:未定义不是函数
这是我的App.js
const AuthenticatedUserProvider = ({ children }) => {
const [user, setUser] = useState(null);
return (
<AuthenticatedUserContext.Provider value={{ user, setUser }}>
{children}
</AuthenticatedUserContext.Provider>
);
};
function RootNavigator() {
const auth = getAuth();
const { user, setUser } = useContext(AuthenticatedUserContext);
const [sesiones, setSesiones] = useState(null);
useEffect(() => {
const storeData = async (user) => {
try {
await AsyncStorage.setItem("cookie", JSON.stringify(user));
} catch (e) {}
};
const obtenerSesion = async () => {
try {
const session = await AsyncStorage.getItem("cookie");
setSesiones(JSON.parse(session));
} catch (e) {}
};
if (user) {
storeData(user);
obtenerSesion();
}
if (!user) {
obtenerSesion();
}
const unsubscribe = onAuthStateChanged(auth, async (authenticatedUser) => {
authenticatedUser ? setUser(authenticatedUser) : setUser(null);
});
return () => unsubscribe();
}, [user]);
return (
<NavigationContainer>
{user || sesiones ? <AppNavigation /> : <GuestNavigation />}
</NavigationContainer>
);
}
export default function App() {
return (
<>
<AuthenticatedUserProvider>
<RootNavigator />
</AuthenticatedUserProvider>
<Toast />
</>
);
}
这是Profile.js 从 profile.js 我通过 Asyncstorage 获取用户的会话,问题是当我想更新个人资料照片时,他的博客告诉你它是这样完成的: updateProfile(auth.currentUser, { photoURL: imageUrl } );
但是我通过 Asyncstorage 获得了会话:
updateProfile(photoURLs, { photoURL: imageUrl });
photoURLs 是保存用户会话的状态,但是在更新时它给了我这个错误: 警告可能未处理的承诺拒绝(id:3): 类型错误:未定义不是函数
const uploadImage = async (uri) => {
setLoading(true);
const imageBlob = [];
const metadata = {
contentType: "image/jpeg",
};
const img = await fetch(uri);
const blob = await img.blob();
const storage = getStorage();
const archivoRef = ref(storage, `avatar/${id()}`);
uploadBytes(archivoRef, blob, metadata).then((snapshot) => {
updatePhotoAvatar(snapshot.metadata.fullPath);
});
};
const updatePhotoAvatar = async (imagePath) => {
try {
const storage = getStorage();
const archivoRef = ref(storage, imagePath);
const imageUrl = await getDownloadURL(archivoRef);
updateProfile(photoURLs, { photoURL: imageUrl });//error line
setAvatar(imageUrl);
setLoading(false);
} catch (error) {
console.log(error);
}
};
当我使用 Asycnstorage 进行会话时,我无法更新个人资料照片,因此使用显示名称时,我会收到相同的错误。 React Native 和 Expo 中的代码
在您的代码中,您已确定这一行是罪魁祸首。
updateProfile(photoURLs, { photoURL: imageUrl });
此行创建了一个浮动 Promise,因为您错过了
await
语句。此外,尚不清楚 photoURLs
是什么 - 它是一个物体吗?这是一个
auth.User
物体?
updateProfile
正常工作,您必须传入 auth.User
对象。
基于这一行,您有时会传入一个
auth.User
对象,有时会传入一个普通 JavaScript 对象(这是 auth.User
的 JSON 可序列化版本):
user || sesiones ? <AppNavigation /> : <GuestNavigation />
为了有效地处理这个问题,我们应该查看您的身份验证相关代码并对其进行修改,以便更加清晰。
首先,您应该将
onAuthStateChanged
逻辑移至 AuthenticatedUserProvider
组件中。这节省了在组件外部处理 setUser
的麻烦,这可能会导致令人困惑的行为。
// ./components/AuthenticatedUserContext.jsx
import { createContext, useContext, useState } from "react";
import { getAuth, User } from "firebase/auth";
export const AuthenticatedUserContext = createContext(null);
export default function AuthenticatedUserProvider({ children }) {
// define state
const auth = getAuth();
const [user, setUser] = useState(() => auth.currentUser || undefined);
// define helpers
const loading = user === undefined;
/** @type {Promise<User | null>} */
const getUser = () => new Promise(resolve => { // <-- this helper is for later
const unsubscribe = onAuthStateChanged(auth, (user) => {
unsubscribe();
resolve(user);
});
});
// define listeners
// Note: You may want to swap out for onIdTokenChanged (by replacing
// "onAuthStateChanged" with "onIdTokenChanged") depending on your use case.
// Swapping means profile updates and token refreshes are detected, not just
// sign-in and sign-out events.
useEffect(() => onAuthStateChanged(auth, setUser), []);
return (
<AuthenticatedUserContext.Provider
value={{
getUser,
loading,
/** @type {User | null} */
user: user || null
}}>
{children}
</AuthenticatedUserContext.Provider>
);
}
在上面的代码中,这一行:
useEffect(() => onAuthStateChanged(auth, setUser), []);
替换:
useEffect(() => {
// ... this code is being moved ...
const unsubscribe = onAuthStateChanged(auth, async (authenticatedUser) => { // <-- shouldn't be async
authenticatedUser ? setUser(authenticatedUser) : setUser(null);
});
return () => unsubscribe();
}, [user]); // <-- this should be [], not [user]
接下来,您应该遵循为该上下文编写自定义挂钩的最佳实践,而不是使用
useContext(AuthenticatedUserContext)
。在下面的代码中,我定义了一个名为 useAuth()
的钩子(而不是更长的 useAuthenticatedUser()
):
// ./components/AuthenticatedUserContext.jsx
/* code from above here */
export function useAuth() {
const context = useContext(AuthenticatedUserContext);
if (context === undefined) throw new Error("Missing parent AuthenticatedUserProvider");
return context;
}
接下来,因为您的
RootNavigator
正在将 auth.User
对象存储到 AsyncStorage
中,所以您应该将此逻辑移至其自己的钩子中,或者使其成为 AuthenticatedUserContext
或 useAuth()
的一部分。我将选择将其放入自己的钩子中,因为这意味着输出 user
对象始终可以是 auth.User
对象的JSON 可序列化版本。如果需要实际的
auth.User
对象(如 updateProfile
中所示),我们将通过 firebaseUser
使其可访问。
在此代码块中,请注意“从 AsyncStorage 读取”和“更新 AsyncStorage”逻辑如何在其自己的使用效果调用中保持分离。这是因为我们只需要从 AsyncStorage 读取一次,但我们可能需要多次更新它。
// ./components/AuthenticatedUserContext.jsx
/* code from above here */
// TODO: When user is signed out, clear cookie from AsyncStorage and set sesiones to `null`
export function useAuthWithAsyncStorage() {
const { getUser, loading: firebaseLoading, user } = useAuth();
const [sesiones, setSesiones] = useState(null);
// define helpers
const loading = firebaseLoading && sesiones == null;
// handles keeping AsyncStorage#cookie updated
useEffect(() => {
if (!user) return; // do nothing, no user to store
const objectUser = user.toJSON();
const storedSesiones = JSON.stringify(objectUser);
// store sesiones object
AsyncStorage.setItem("cookie", storedSesiones)
.catch((e) => { /* ignore */ });
// update sesiones object
setSesiones(objectUser);
}, [user]);
// handles reading from AsyncStorage#cookie once, only when first attached.
// any further updates are handled by the useEffect above.
useEffect(() => {
let unsubscribed = false;
AsyncStorage.getItem("cookie")
.then((value) => {
if (!unsubscribed) return; // unsubscribed, do nothing
setSesiones(JSON.parse(value))
})
.catch((e) => { /* ignore */ });
return () => unsubscribed = true;
}, []);
return {
firebaseUser: user, // pass through in case some of its methods are needed
getUser,
loading,
user: user && user.toJSON() || sesiones
}
}
现在通过
AuthenticatedUserContext
处理此问题,您可以将 RootNavigator
组件简化为:
// ./components/RootNavigator.jsx
import { useState } from "react";
import { useAuthWithAsyncStorage } from "./AuthenticatedUserContext";
export default function RootNavigator() {
const { user } = useAuthWithAsyncStorage();
return (
<NavigationContainer>
{user ? <AppNavigation /> : <GuestNavigation />}
</NavigationContainer>
);
}
// ./App.jsx
import AuthenticatedUserProvider from "./components/AuthenticatedUserContext";
export default function App() {
return (
<>
<AuthenticatedUserProvider>
<RootNavigator />
</AuthenticatedUserProvider>
<Toast />
</>
);
}
回到您的问题组件,您现在可以将代码更新为:
const { getUser } = useAuthWithAsyncStorage();
// Note: `firebaseUser` may be null if the user is signed out or
// Firebase is still initializing, so we use getUser instead. This
// guarantees that we can talk to Firebase to perform the update.
const updatePhotoAvatar = async (imagePath) => {
try {
const firebaseUser = await getUser();
if (!firebaseUser) throw new Error("Not logged in on server!");
const storage = getStorage();
const archivoRef = ref(storage, imagePath);
const imageUrl = await getDownloadURL(archivoRef);
await updateProfile(firebaseUser, { photoURL: imageUrl });
setAvatar(imageUrl);
setLoading(false);
} catch (error) {
console.log(error);
}
};