27
loading...
This website collects cookies to deliver better user experience
onAuthStateChanged
- an observer for changes to the user's "signed-in" state. It will either return a User object or null
, which is perfect for passing straight into a setUser
function.updateUser
function is a bit trickier. I wanted to have a single function that I could pass a displayName
, email
, password
or whatever other properties we could store on a User object. Firebase actually has different update functions for each of these. In the new V9, they're aptly named updateEmail
, updatePassword
and then updateProfile
for everything else (name, photo, etc.)import {
getAuth,
onAuthStateChanged,
signOut,
updateProfile,
updateEmail,
updatePassword,
} from 'firebase/auth';
import { useState } from 'react';
import type { User } from 'firebase/auth';
type UpdateUserProps = {
displayName?: User['displayName'];
photoURL?: User['photoURL'];
email?: User['email'];
password?: string | null;
};
const useUser = (): {
user?: User | null;
updateUser: (props: UpdateUserProps) => Promise<void>;
logout: () => Promise<void>;
} => {
const auth = getAuth();
const [user, setUser] = useState<User | null>(auth.currentUser);
onAuthStateChanged(auth, setUser);
const updateUser = async ({
displayName,
photoURL,
email,
password,
}: UpdateUserProps) => {
if (!user) {
return;
}
if (displayName) {
await updateProfile(user, { displayName });
}
if (photoURL) {
await updateProfile(user, { photoURL });
}
if (email) {
await updateEmail(user, email);
}
if (password) {
await updatePassword(user, password);
}
};
const logout = async () => {
await signOut(auth);
};
return { user, updateUser, logout };
};
export default useUser;
Profile
collection in Firestore that contains any other info you want to store in relation to a particular user. It's also common practice to use the format users/${user.uid}
for the collection path, so we'll make sure to accept a User object as a prop.onSnapshot
that attaches a listener for DocumentSnapshot
events, which is a fancy way of saying it subscribes to a collection and listens for updates. That function takes a document reference (or query), a "next" callback (for success) and an "error" callback. It also takes an "onComplete" callback but since the snapshot stream never ends, it's never called, so 🤷♀️.useEffect
function, remembering to cleanup your snapshot at the end (it returns an unsubscribe function 👍). For the dependency array, we want to pass the User's UID so it re-runs every time the user changes (which is handy for clearing the profile data when the user logs out).import type { User } from 'firebase/auth';
import { getApp } from 'firebase/app';
import { doc, updateDoc, getFirestore, onSnapshot } from 'firebase/firestore';
import type { FirestoreError } from 'firebase/firestore';
import { useEffect, useState } from 'react';
// Whatever your profile looks like!
export type ProfileProps = {};
type UseProfileResponse = {
profile: ProfileProps | null | undefined;
updateProfile: (newData: Partial<ProfileProps>) => Promise<void>;
profileLoading: boolean;
profileError: FirestoreError | undefined;
};
const useProfile = (
user: Partial<User> | null | undefined
): UseProfileResponse => {
const app = getApp();
const firestore = getFirestore(app);
const [profile, setProfile] = useState<ProfileProps | null>(null);
const [profileError, setProfileError] = useState<
FirestoreError | undefined
>();
const [profileLoading, setProfileLoading] = useState(false);
useEffect(() => {
if (!user?.uid) {
setProfile(null);
return undefined;
}
setProfileLoading(true);
const profileRef = doc(firestore, 'users', user.uid);
const unsubscribe = onSnapshot(
profileRef,
(profileDoc) => {
setProfile(profileDoc.data() as ProfileProps);
setProfileLoading(false);
},
setProfileError
);
return unsubscribe;
}, [firestore, user?.uid]);
const updateProfile = async (
newData: Partial<ProfileProps>
): Promise<void> => {
if (!user?.uid) {
return;
}
const profileRef = doc(firestore, 'users', user.uid);
await updateDoc(profileRef, newData);
};
return {
profile,
updateProfile,
profileLoading,
profileError,
};
};
export default useProfile;