expo-starter/providers/AuthProvider.tsx

86 lines
2.0 KiB
TypeScript

import {
createContext,
useContext,
useEffect,
useMemo,
useState,
type PropsWithChildren,
} from 'react';
import { onAuthStateChanged, type User as FirebaseAuthUser } from 'firebase/auth';
import { onSnapshot } from 'firebase/firestore';
import { auth } from '@/lib/firebase';
import { userDoc } from '@/lib/firestore';
import type { UserDocument } from '@/types/firestore';
type AuthContextValue = {
authUser: FirebaseAuthUser | null;
user: UserDocument | null;
initializing: boolean;
};
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
export function AuthProvider({ children }: PropsWithChildren) {
const [authUser, setAuthUser] = useState<FirebaseAuthUser | null>(null);
const [user, setUser] = useState<UserDocument | null>(null);
const [initializing, setInitializing] = useState(true);
useEffect(() => {
let unsubscribeUserSnapshot: (() => void) | undefined;
const unsubscribe = onAuthStateChanged(auth, (nextUser) => {
setAuthUser(nextUser);
if (unsubscribeUserSnapshot) {
unsubscribeUserSnapshot();
unsubscribeUserSnapshot = undefined;
}
if (!nextUser) {
setUser(null);
setInitializing(false);
return;
}
unsubscribeUserSnapshot = onSnapshot(
userDoc(nextUser.uid),
(snapshot) => {
setUser(snapshot.data() ?? null);
setInitializing(false);
},
() => {
setUser(null);
setInitializing(false);
}
);
});
return () => {
unsubscribe();
unsubscribeUserSnapshot?.();
};
}, []);
const value = useMemo(
() => ({
authUser,
user,
initializing,
}),
[authUser, initializing, user]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}