Move FirebaseUser to context to avoid duplicated subscriptions

This commit is contained in:
MartinBraquet
2026-02-13 22:55:02 +01:00
parent 6815513ac3
commit 2c3314becc
3 changed files with 38 additions and 33 deletions

View File

@@ -1,7 +1,7 @@
'use client'
import {createContext, ReactNode, useEffect, useState} from 'react'
import {pickBy} from 'lodash'
import {onIdTokenChanged, User as FirebaseUser} from 'firebase/auth'
import {onAuthStateChanged, onIdTokenChanged, User as FirebaseUser} from 'firebase/auth'
import {auth} from 'web/lib/firebase/users'
import {api} from 'web/lib/api'
import {randomString} from 'common/util/random'
@@ -20,7 +20,7 @@ import {identifyUser, setUserProperty} from 'web/lib/service/analytics'
export type AuthUser =
| undefined
| null
| (UserAndPrivateUser & { authLoaded: boolean })
| (UserAndPrivateUser & { authLoaded: boolean, firebaseUser: FirebaseUser })
const CACHED_USER_KEY = 'CACHED_USER_KEY_V2'
export const ensureDeviceToken = () => {
@@ -74,6 +74,35 @@ export const clearUserCookie = () => {
])
}
/**
* Subscribe to Firebase Auth user updates.
* Reactively returns the current Firebase `User` and updates when:
* - auth state changes (sign in/out)
* - ID token changes (after `getIdToken(true)` or `user.reload()`),
* which is important for reflecting `emailVerified` changes without a hard refresh.
*/
export function useAndSetupFirebaseUser() {
const [, forceRender] = useState(0);
const [firebaseUser, setFirebaseUser] = useState<FirebaseUser | null>(auth.currentUser);
useEffect(() => {
const update = (u: FirebaseUser | null) => {
setFirebaseUser(u); // keep the real User instance
forceRender(v => v + 1); // force React to re-render
};
const unsubAuth = onAuthStateChanged(auth, update);
const unsubToken = onIdTokenChanged(auth, update);
return () => {
unsubAuth();
unsubToken();
};
}, []);
return firebaseUser;
}
export const AuthContext = createContext<AuthUser>(undefined)
@@ -90,12 +119,13 @@ export function AuthProvider(props: {
PrivateUser | undefined
>(serverUser ? serverUser.privateUser : undefined)
const [authLoaded, setAuthLoaded] = useState(false)
const firebaseUser = useAndSetupFirebaseUser()
const authUser = !user
? user
: !privateUser
? privateUser
: {user, privateUser, authLoaded}
: firebaseUser ? {user, privateUser, authLoaded, firebaseUser} : undefined
useEffect(() => {
if (serverUser === undefined) {

View File

@@ -1,32 +1,7 @@
import {useEffect, useState} from 'react'
import {onAuthStateChanged, onIdTokenChanged, User} from 'firebase/auth'
import {auth} from 'web/lib/firebase/users'
import {useContext} from "react";
import {AuthContext} from "web/components/auth-context";
/**
* Subscribe to Firebase Auth user updates.
* Reactively returns the current Firebase `User` and updates when:
* - auth state changes (sign in/out)
* - ID token changes (after `getIdToken(true)` or `user.reload()`),
* which is important for reflecting `emailVerified` changes without a hard refresh.
*/
export function useFirebaseUser() {
const [, forceRender] = useState(0);
const [firebaseUser, setFirebaseUser] = useState<User | null>(auth.currentUser);
useEffect(() => {
const update = (u: User | null) => {
setFirebaseUser(u); // keep the real User instance
forceRender(v => v + 1); // force React to re-render
};
const unsubAuth = onAuthStateChanged(auth, update);
const unsubToken = onIdTokenChanged(auth, update);
return () => {
unsubAuth();
unsubToken();
};
}, []);
return firebaseUser;
const ctx = useContext(AuthContext)
return ctx?.firebaseUser
}

View File

@@ -3,7 +3,7 @@ import {sendEmailVerification, User} from "firebase/auth";
export const sendVerificationEmail = async (
user: User | null,
user: User | null | undefined,
t: any
) => {
// if (!privateUser?.email) {