From c334491f19f23023d1a27ea69c98b183d6abc0e1 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:10:27 +0000 Subject: [PATCH 1/2] feat: replace rxfire sync engine with useFirebaseStream (v4.3.0) --- docs/reference/README.md | 155 ++--- docs/reference/classes/ReactFireError.md | 30 +- docs/reference/enumerations/ListenEvent.md | 53 ++ docs/reference/functions/AuthCheck.md | 14 +- docs/reference/functions/ClaimsCheck.md | 4 +- .../functions/FirebaseAppProvider.md | 8 +- docs/reference/functions/StorageImage.md | 8 +- docs/reference/functions/SuspenseWithPerf.md | 2 +- docs/reference/functions/checkIdField.md | 8 +- docs/reference/functions/checkOptions.md | 8 +- docs/reference/functions/checkinitialData.md | 8 +- .../functions/preloadFirestoreDoc.md | 12 +- docs/reference/functions/preloadObservable.md | 12 +- docs/reference/functions/preloadUser.md | 12 +- docs/reference/functions/useAnalytics.md | 8 +- docs/reference/functions/useAppCheck.md | 8 +- docs/reference/functions/useAuth.md | 8 +- .../functions/useCallableFunctionResponse.md | 8 +- docs/reference/functions/useDatabase.md | 8 +- docs/reference/functions/useDatabaseList.md | 8 +- .../functions/useDatabaseListData.md | 12 +- docs/reference/functions/useDatabaseObject.md | 6 +- .../functions/useDatabaseObjectData.md | 10 +- docs/reference/functions/useFirebaseApp.md | 8 +- docs/reference/functions/useFirestore.md | 8 +- .../functions/useFirestoreCollection.md | 2 +- .../functions/useFirestoreCollectionData.md | 8 +- docs/reference/functions/useFirestoreDoc.md | 2 +- .../functions/useFirestoreDocData.md | 8 +- .../functions/useFirestoreDocDataOnce.md | 8 +- .../functions/useFirestoreDocOnce.md | 8 +- docs/reference/functions/useFunctions.md | 8 +- docs/reference/functions/useIdTokenResult.md | 40 +- .../functions/useIsSuspenseEnabled.md | 8 +- docs/reference/functions/useObservable.md | 10 +- docs/reference/functions/usePerformance.md | 8 +- docs/reference/functions/useRemoteConfig.md | 8 +- .../reference/functions/useRemoteConfigAll.md | 12 +- .../functions/useRemoteConfigBoolean.md | 8 +- .../functions/useRemoteConfigNumber.md | 8 +- .../functions/useRemoteConfigString.md | 8 +- .../functions/useRemoteConfigValue.md | 8 +- docs/reference/functions/useSigninCheck.md | 36 +- docs/reference/functions/useStorage.md | 8 +- .../functions/useStorageDownloadURL.md | 8 +- docs/reference/functions/useStorageTask.md | 12 +- .../useSuspenseEnabledFromConfigAndContext.md | 8 +- docs/reference/functions/useUser.md | 42 +- docs/reference/interfaces/AuthCheckProps.md | 16 +- docs/reference/interfaces/ClaimCheckErrors.md | 8 +- docs/reference/interfaces/ClaimsCheckProps.md | 24 +- docs/reference/interfaces/ClaimsValidator.md | 18 +- .../interfaces/FirebaseAppProviderProps.md | 28 +- docs/reference/interfaces/ObservableStatus.md | 64 +- docs/reference/interfaces/QueryChange.md | 37 ++ docs/reference/interfaces/ReactFireOptions.md | 44 +- .../interfaces/SignInCheckOptionsBasic.md | 64 +- .../SignInCheckOptionsClaimsObject.md | 68 +- .../SignInCheckOptionsClaimsValidator.md | 68 +- .../reference/interfaces/SuspensePerfProps.md | 18 +- docs/reference/type-aliases/AllParameters.md | 19 + .../type-aliases/ReactFireGlobals.md | 20 +- .../reference/type-aliases/ReactFireUsable.md | 20 + .../type-aliases/SigninCheckResult.md | 8 +- .../type-aliases/StorageImageProps.md | 24 +- docs/reference/variables/AnalyticsProvider.md | 8 +- .../variables/AnalyticsSdkContext.md | 8 +- docs/reference/variables/AppCheckProvider.md | 8 +- .../reference/variables/AppCheckSdkContext.md | 8 +- docs/reference/variables/AuthProvider.md | 8 +- docs/reference/variables/AuthSdkContext.md | 6 +- docs/reference/variables/DatabaseProvider.md | 8 +- .../reference/variables/DatabaseSdkContext.md | 8 +- docs/reference/variables/FirestoreProvider.md | 8 +- .../variables/FirestoreSdkContext.md | 8 +- docs/reference/variables/FunctionsProvider.md | 8 +- .../variables/FunctionsSdkContext.md | 8 +- .../variables/PerformanceProvider.md | 8 +- .../variables/PerformanceSdkContext.md | 8 +- .../variables/RemoteConfigProvider.md | 8 +- .../variables/RemoteConfigSdkContext.md | 8 +- docs/reference/variables/StorageProvider.md | 8 +- docs/reference/variables/StorageSdkContext.md | 8 +- docs/reference/variables/useInitAnalytics.md | 8 +- docs/reference/variables/useInitAppCheck.md | 8 +- docs/reference/variables/useInitAuth.md | 8 +- docs/reference/variables/useInitDatabase.md | 8 +- docs/reference/variables/useInitFirestore.md | 8 +- docs/reference/variables/useInitFunctions.md | 8 +- .../reference/variables/useInitPerformance.md | 8 +- .../variables/useInitRemoteConfig.md | 8 +- docs/reference/variables/useInitStorage.md | 8 +- docs/reference/variables/version.md | 2 +- package-lock.json | 112 +++- package.json | 2 +- src/SuspenseSubject.ts | 57 +- src/auth.tsx | 603 ++++++++++++++---- src/database.tsx | 234 ++++++- src/firebaseApp.tsx | 25 +- src/firestore.tsx | 344 ++++++++-- src/functions.tsx | 73 ++- src/index.ts | 50 +- src/performance.tsx | 63 +- src/remote-config.tsx | 100 ++- src/sdk.tsx | 73 ++- src/storage.tsx | 172 ++++- src/useFirebaseStream.ts | 566 ++++++++++++++++ src/useObservable.ts | 119 ++-- test/auth.test.tsx | 91 +++ test/database.test.tsx | 3 +- test/firebaseApp.test.tsx | 33 +- test/firestore.test.tsx | 172 ++++- test/performance.test.tsx | 43 ++ vite.config.ts | 2 + 114 files changed, 3602 insertions(+), 801 deletions(-) create mode 100644 docs/reference/enumerations/ListenEvent.md create mode 100644 docs/reference/interfaces/QueryChange.md create mode 100644 docs/reference/type-aliases/AllParameters.md create mode 100644 docs/reference/type-aliases/ReactFireUsable.md create mode 100644 src/useFirebaseStream.ts diff --git a/docs/reference/README.md b/docs/reference/README.md index 52c83109..e7d24aef 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -4,105 +4,112 @@ # ReactFire reference docs +## Enumerations + +- [~~ListenEvent~~](enumerations/ListenEvent.md) + ## Classes -- [ReactFireError](classes/ReactFireError.md) +- [~~ReactFireError~~](classes/ReactFireError.md) ## Interfaces - [AuthCheckProps](interfaces/AuthCheckProps.md) -- [ClaimCheckErrors](interfaces/ClaimCheckErrors.md) -- [ClaimsCheckProps](interfaces/ClaimsCheckProps.md) -- [ClaimsValidator](interfaces/ClaimsValidator.md) -- [FirebaseAppProviderProps](interfaces/FirebaseAppProviderProps.md) +- [~~ClaimCheckErrors~~](interfaces/ClaimCheckErrors.md) +- [~~ClaimsCheckProps~~](interfaces/ClaimsCheckProps.md) +- [~~ClaimsValidator~~](interfaces/ClaimsValidator.md) +- [~~FirebaseAppProviderProps~~](interfaces/FirebaseAppProviderProps.md) - [ObservableStatus](interfaces/ObservableStatus.md) +- [~~QueryChange~~](interfaces/QueryChange.md) - [ReactFireOptions](interfaces/ReactFireOptions.md) -- [SignInCheckOptionsBasic](interfaces/SignInCheckOptionsBasic.md) -- [SignInCheckOptionsClaimsObject](interfaces/SignInCheckOptionsClaimsObject.md) -- [SignInCheckOptionsClaimsValidator](interfaces/SignInCheckOptionsClaimsValidator.md) +- [~~SignInCheckOptionsBasic~~](interfaces/SignInCheckOptionsBasic.md) +- [~~SignInCheckOptionsClaimsObject~~](interfaces/SignInCheckOptionsClaimsObject.md) +- [~~SignInCheckOptionsClaimsValidator~~](interfaces/SignInCheckOptionsClaimsValidator.md) - [SuspensePerfProps](interfaces/SuspensePerfProps.md) ## Type Aliases -- [ReactFireGlobals](type-aliases/ReactFireGlobals.md) -- [SigninCheckResult](type-aliases/SigninCheckResult.md) -- [StorageImageProps](type-aliases/StorageImageProps.md) +- [~~AllParameters~~](type-aliases/AllParameters.md) +- [~~ReactFireGlobals~~](type-aliases/ReactFireGlobals.md) +- [ReactFireUsable](type-aliases/ReactFireUsable.md) +- [~~SigninCheckResult~~](type-aliases/SigninCheckResult.md) +- [~~StorageImageProps~~](type-aliases/StorageImageProps.md) ## Variables -- [AnalyticsProvider](variables/AnalyticsProvider.md) -- [AnalyticsSdkContext](variables/AnalyticsSdkContext.md) -- [AppCheckProvider](variables/AppCheckProvider.md) -- [AppCheckSdkContext](variables/AppCheckSdkContext.md) -- [AuthProvider](variables/AuthProvider.md) -- [AuthSdkContext](variables/AuthSdkContext.md) -- [DatabaseProvider](variables/DatabaseProvider.md) -- [DatabaseSdkContext](variables/DatabaseSdkContext.md) -- [FirestoreProvider](variables/FirestoreProvider.md) -- [FirestoreSdkContext](variables/FirestoreSdkContext.md) -- [FunctionsProvider](variables/FunctionsProvider.md) -- [FunctionsSdkContext](variables/FunctionsSdkContext.md) -- [PerformanceProvider](variables/PerformanceProvider.md) -- [PerformanceSdkContext](variables/PerformanceSdkContext.md) -- [RemoteConfigProvider](variables/RemoteConfigProvider.md) -- [RemoteConfigSdkContext](variables/RemoteConfigSdkContext.md) -- [StorageProvider](variables/StorageProvider.md) -- [StorageSdkContext](variables/StorageSdkContext.md) -- [useInitAnalytics](variables/useInitAnalytics.md) -- [useInitAppCheck](variables/useInitAppCheck.md) -- [useInitAuth](variables/useInitAuth.md) -- [useInitDatabase](variables/useInitDatabase.md) -- [useInitFirestore](variables/useInitFirestore.md) -- [useInitFunctions](variables/useInitFunctions.md) -- [useInitPerformance](variables/useInitPerformance.md) -- [useInitRemoteConfig](variables/useInitRemoteConfig.md) -- [useInitStorage](variables/useInitStorage.md) +- [~~AnalyticsProvider~~](variables/AnalyticsProvider.md) +- [~~AnalyticsSdkContext~~](variables/AnalyticsSdkContext.md) +- [~~AppCheckProvider~~](variables/AppCheckProvider.md) +- [~~AppCheckSdkContext~~](variables/AppCheckSdkContext.md) +- [~~AuthProvider~~](variables/AuthProvider.md) +- [~~AuthSdkContext~~](variables/AuthSdkContext.md) +- [~~DatabaseProvider~~](variables/DatabaseProvider.md) +- [~~DatabaseSdkContext~~](variables/DatabaseSdkContext.md) +- [~~FirestoreProvider~~](variables/FirestoreProvider.md) +- [~~FirestoreSdkContext~~](variables/FirestoreSdkContext.md) +- [~~FunctionsProvider~~](variables/FunctionsProvider.md) +- [~~FunctionsSdkContext~~](variables/FunctionsSdkContext.md) +- [~~PerformanceProvider~~](variables/PerformanceProvider.md) +- [~~PerformanceSdkContext~~](variables/PerformanceSdkContext.md) +- [~~RemoteConfigProvider~~](variables/RemoteConfigProvider.md) +- [~~RemoteConfigSdkContext~~](variables/RemoteConfigSdkContext.md) +- [~~StorageProvider~~](variables/StorageProvider.md) +- [~~StorageSdkContext~~](variables/StorageSdkContext.md) +- [~~useInitAnalytics~~](variables/useInitAnalytics.md) +- [~~useInitAppCheck~~](variables/useInitAppCheck.md) +- [~~useInitAuth~~](variables/useInitAuth.md) +- [~~useInitDatabase~~](variables/useInitDatabase.md) +- [~~useInitFirestore~~](variables/useInitFirestore.md) +- [~~useInitFunctions~~](variables/useInitFunctions.md) +- [~~useInitPerformance~~](variables/useInitPerformance.md) +- [~~useInitRemoteConfig~~](variables/useInitRemoteConfig.md) +- [~~useInitStorage~~](variables/useInitStorage.md) - [version](variables/version.md) ## Functions -- [~~AuthCheck~~](functions/AuthCheck.md) -- [checkIdField](functions/checkIdField.md) -- [checkinitialData](functions/checkinitialData.md) -- [checkOptions](functions/checkOptions.md) +- [AuthCheck](functions/AuthCheck.md) +- [~~checkIdField~~](functions/checkIdField.md) +- [~~checkinitialData~~](functions/checkinitialData.md) +- [~~checkOptions~~](functions/checkOptions.md) - [~~ClaimsCheck~~](functions/ClaimsCheck.md) -- [FirebaseAppProvider](functions/FirebaseAppProvider.md) -- [preloadFirestoreDoc](functions/preloadFirestoreDoc.md) -- [preloadObservable](functions/preloadObservable.md) -- [preloadUser](functions/preloadUser.md) -- [StorageImage](functions/StorageImage.md) +- [~~FirebaseAppProvider~~](functions/FirebaseAppProvider.md) +- [~~preloadFirestoreDoc~~](functions/preloadFirestoreDoc.md) +- [~~preloadObservable~~](functions/preloadObservable.md) +- [~~preloadUser~~](functions/preloadUser.md) +- [~~StorageImage~~](functions/StorageImage.md) - [SuspenseWithPerf](functions/SuspenseWithPerf.md) -- [useAnalytics](functions/useAnalytics.md) -- [useAppCheck](functions/useAppCheck.md) -- [useAuth](functions/useAuth.md) -- [useCallableFunctionResponse](functions/useCallableFunctionResponse.md) -- [useDatabase](functions/useDatabase.md) +- [~~useAnalytics~~](functions/useAnalytics.md) +- [~~useAppCheck~~](functions/useAppCheck.md) +- [~~useAuth~~](functions/useAuth.md) +- [~~useCallableFunctionResponse~~](functions/useCallableFunctionResponse.md) +- [~~useDatabase~~](functions/useDatabase.md) - [useDatabaseList](functions/useDatabaseList.md) -- [useDatabaseListData](functions/useDatabaseListData.md) +- [~~useDatabaseListData~~](functions/useDatabaseListData.md) - [useDatabaseObject](functions/useDatabaseObject.md) -- [useDatabaseObjectData](functions/useDatabaseObjectData.md) -- [useFirebaseApp](functions/useFirebaseApp.md) -- [useFirestore](functions/useFirestore.md) +- [~~useDatabaseObjectData~~](functions/useDatabaseObjectData.md) +- [~~useFirebaseApp~~](functions/useFirebaseApp.md) +- [~~useFirestore~~](functions/useFirestore.md) - [useFirestoreCollection](functions/useFirestoreCollection.md) -- [useFirestoreCollectionData](functions/useFirestoreCollectionData.md) +- [~~useFirestoreCollectionData~~](functions/useFirestoreCollectionData.md) - [useFirestoreDoc](functions/useFirestoreDoc.md) -- [useFirestoreDocData](functions/useFirestoreDocData.md) -- [useFirestoreDocDataOnce](functions/useFirestoreDocDataOnce.md) -- [useFirestoreDocOnce](functions/useFirestoreDocOnce.md) -- [useFunctions](functions/useFunctions.md) +- [~~useFirestoreDocData~~](functions/useFirestoreDocData.md) +- [~~useFirestoreDocDataOnce~~](functions/useFirestoreDocDataOnce.md) +- [~~useFirestoreDocOnce~~](functions/useFirestoreDocOnce.md) +- [~~useFunctions~~](functions/useFunctions.md) - [useIdTokenResult](functions/useIdTokenResult.md) -- [useIsSuspenseEnabled](functions/useIsSuspenseEnabled.md) -- [useObservable](functions/useObservable.md) -- [usePerformance](functions/usePerformance.md) -- [useRemoteConfig](functions/useRemoteConfig.md) -- [useRemoteConfigAll](functions/useRemoteConfigAll.md) -- [useRemoteConfigBoolean](functions/useRemoteConfigBoolean.md) -- [useRemoteConfigNumber](functions/useRemoteConfigNumber.md) -- [useRemoteConfigString](functions/useRemoteConfigString.md) -- [useRemoteConfigValue](functions/useRemoteConfigValue.md) -- [useSigninCheck](functions/useSigninCheck.md) -- [useStorage](functions/useStorage.md) +- [~~useIsSuspenseEnabled~~](functions/useIsSuspenseEnabled.md) +- [~~useObservable~~](functions/useObservable.md) +- [~~usePerformance~~](functions/usePerformance.md) +- [~~useRemoteConfig~~](functions/useRemoteConfig.md) +- [~~useRemoteConfigAll~~](functions/useRemoteConfigAll.md) +- [~~useRemoteConfigBoolean~~](functions/useRemoteConfigBoolean.md) +- [~~useRemoteConfigNumber~~](functions/useRemoteConfigNumber.md) +- [~~useRemoteConfigString~~](functions/useRemoteConfigString.md) +- [~~useRemoteConfigValue~~](functions/useRemoteConfigValue.md) +- [~~useSigninCheck~~](functions/useSigninCheck.md) +- [~~useStorage~~](functions/useStorage.md) - [useStorageDownloadURL](functions/useStorageDownloadURL.md) -- [useStorageTask](functions/useStorageTask.md) -- [useSuspenseEnabledFromConfigAndContext](functions/useSuspenseEnabledFromConfigAndContext.md) +- [~~useStorageTask~~](functions/useStorageTask.md) +- [~~useSuspenseEnabledFromConfigAndContext~~](functions/useSuspenseEnabledFromConfigAndContext.md) - [useUser](functions/useUser.md) diff --git a/docs/reference/classes/ReactFireError.md b/docs/reference/classes/ReactFireError.md index 591ddb68..89abe675 100644 --- a/docs/reference/classes/ReactFireError.md +++ b/docs/reference/classes/ReactFireError.md @@ -4,9 +4,13 @@ [ReactFire reference docs](../README.md) / ReactFireError -# Class: ReactFireError +# ~~Class: ReactFireError~~ -Defined in: [src/index.ts:12](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L12) +Defined in: [src/useFirebaseStream.ts:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L20) + +## Deprecated + +Error class used only by the deprecated `useSigninCheck` hook. Will be removed in v5.0.0. ## Extends @@ -18,7 +22,7 @@ Defined in: [src/index.ts:12](https://github.com/FirebaseExtended/reactfire/blob > **new ReactFireError**(`code`, `message`, `customData?`): `ReactFireError` -Defined in: [src/index.ts:15](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L15) +Defined in: [src/useFirebaseStream.ts:23](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L23) #### Parameters @@ -44,7 +48,7 @@ Defined in: [src/index.ts:15](https://github.com/FirebaseExtended/reactfire/blob ## Properties -### cause? +### ~~cause?~~ > `optional` **cause?**: `unknown` @@ -56,23 +60,23 @@ Defined in: node\_modules/typescript/lib/lib.es2022.error.d.ts:24 *** -### code +### ~~code~~ > `readonly` **code**: `string` -Defined in: [src/index.ts:15](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L15) +Defined in: [src/useFirebaseStream.ts:23](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L23) *** -### customData? +### ~~customData?~~ > `optional` **customData?**: `Record`\<`string`, `unknown`\> -Defined in: [src/index.ts:15](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L15) +Defined in: [src/useFirebaseStream.ts:23](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L23) *** -### message +### ~~message~~ > **message**: `string` @@ -84,11 +88,11 @@ Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1075 *** -### name +### ~~name~~ > `readonly` **name**: `"ReactFireError"` = `'ReactFireError'` -Defined in: [src/index.ts:13](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L13) +Defined in: [src/useFirebaseStream.ts:21](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L21) #### Overrides @@ -96,7 +100,7 @@ Defined in: [src/index.ts:13](https://github.com/FirebaseExtended/reactfire/blob *** -### stack? +### ~~stack?~~ > `optional` **stack?**: `string` @@ -108,7 +112,7 @@ Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1076 ## Methods -### isError() +### ~~isError()~~ > `static` **isError**(`error`): `error is Error` diff --git a/docs/reference/enumerations/ListenEvent.md b/docs/reference/enumerations/ListenEvent.md new file mode 100644 index 00000000..3ac54b15 --- /dev/null +++ b/docs/reference/enumerations/ListenEvent.md @@ -0,0 +1,53 @@ +[**ReactFire reference docs**](../README.md) + +*** + +[ReactFire reference docs](../README.md) / ListenEvent + +# ~~Enumeration: ListenEvent~~ + +Defined in: [src/database.tsx:16](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L16) + +## Deprecated + +Legacy rxfire change event enum. In v5.0.0, `useDatabaseObject` returns `DataSnapshot` and `useDatabaseList` returns `DataSnapshot[]`. + +## Enumeration Members + +### ~~added~~ + +> **added**: `"child_added"` + +Defined in: [src/database.tsx:17](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L17) + +*** + +### ~~changed~~ + +> **changed**: `"child_changed"` + +Defined in: [src/database.tsx:19](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L19) + +*** + +### ~~moved~~ + +> **moved**: `"child_moved"` + +Defined in: [src/database.tsx:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L20) + +*** + +### ~~removed~~ + +> **removed**: `"child_removed"` + +Defined in: [src/database.tsx:18](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L18) + +*** + +### ~~value~~ + +> **value**: `"value"` + +Defined in: [src/database.tsx:21](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L21) diff --git a/docs/reference/functions/AuthCheck.md b/docs/reference/functions/AuthCheck.md index dfb0e3ea..6f0b573e 100644 --- a/docs/reference/functions/AuthCheck.md +++ b/docs/reference/functions/AuthCheck.md @@ -4,11 +4,13 @@ [ReactFire reference docs](../README.md) / AuthCheck -# ~~Function: AuthCheck()~~ +# Function: AuthCheck() > **AuthCheck**(`__namedParameters`): `ReactElement` -Defined in: [src/auth.tsx:259](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L259) +Defined in: [src/auth.tsx:638](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L638) + +Conditionally render children based on signed-in status and [custom claims](https://firebase.google.com/docs/auth/admin/custom-claims). ## Parameters @@ -19,11 +21,3 @@ Defined in: [src/auth.tsx:259](https://github.com/FirebaseExtended/reactfire/blo ## Returns `ReactElement` - -## Deprecated - -Use `useSigninCheck` instead - -Conditionally render children based on signed-in status and [custom claims](https://firebase.google.com/docs/auth/admin/custom-claims). - -Meant for Concurrent mode only (``). [More detail](https://github.com/FirebaseExtended/reactfire/issues/325#issuecomment-827654376). diff --git a/docs/reference/functions/ClaimsCheck.md b/docs/reference/functions/ClaimsCheck.md index fa5ca95e..07cbc7ca 100644 --- a/docs/reference/functions/ClaimsCheck.md +++ b/docs/reference/functions/ClaimsCheck.md @@ -8,7 +8,7 @@ > **ClaimsCheck**(`__namedParameters`): `Element` -Defined in: [src/auth.tsx:215](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L215) +Defined in: [src/auth.tsx:598](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L598) ## Parameters @@ -22,7 +22,7 @@ Defined in: [src/auth.tsx:215](https://github.com/FirebaseExtended/reactfire/blo ## Deprecated -Use `useSigninCheck` instead +Use `` instead. Will be removed in v5.0.0. Conditionally render children based on [custom claims](https://firebase.google.com/docs/auth/admin/custom-claims). diff --git a/docs/reference/functions/FirebaseAppProvider.md b/docs/reference/functions/FirebaseAppProvider.md index 6b851653..71058846 100644 --- a/docs/reference/functions/FirebaseAppProvider.md +++ b/docs/reference/functions/FirebaseAppProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / FirebaseAppProvider -# Function: FirebaseAppProvider() +# ~~Function: FirebaseAppProvider()~~ > **FirebaseAppProvider**(`props`): `Element` -Defined in: [src/firebaseApp.tsx:26](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L26) +Defined in: [src/firebaseApp.tsx:35](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L35) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/firebaseApp.tsx:26](https://github.com/FirebaseExtended/reactfi ## Returns `Element` + +## Deprecated + +`` is deprecated and will be removed in v5.0.0. Initialize your Firebase app at module scope (`export const app = initializeApp(config)`) and pass SDK instances or references directly to ReactFire hooks. diff --git a/docs/reference/functions/StorageImage.md b/docs/reference/functions/StorageImage.md index 59c9a60b..aa564797 100644 --- a/docs/reference/functions/StorageImage.md +++ b/docs/reference/functions/StorageImage.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / StorageImage -# Function: StorageImage() +# ~~Function: StorageImage()~~ > **StorageImage**(`props`): `Element` -Defined in: [src/storage.tsx:82](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L82) +Defined in: [src/storage.tsx:214](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L214) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/storage.tsx:82](https://github.com/FirebaseExtended/reactfire/b ## Returns `Element` + +## Deprecated + +Use `useStorageDownloadURL(ref)` with a standard `` element instead. Will be removed in v5.0.0. diff --git a/docs/reference/functions/SuspenseWithPerf.md b/docs/reference/functions/SuspenseWithPerf.md index 6ab06a72..71a87b6c 100644 --- a/docs/reference/functions/SuspenseWithPerf.md +++ b/docs/reference/functions/SuspenseWithPerf.md @@ -8,7 +8,7 @@ > **SuspenseWithPerf**(`__namedParameters`): `ReactElement` -Defined in: [src/performance.tsx:9](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L9) +Defined in: [src/performance.tsx:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L45) ## Parameters diff --git a/docs/reference/functions/checkIdField.md b/docs/reference/functions/checkIdField.md index 82d7c5a9..2a1f0332 100644 --- a/docs/reference/functions/checkIdField.md +++ b/docs/reference/functions/checkIdField.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / checkIdField -# Function: checkIdField() +# ~~Function: checkIdField()~~ > **checkIdField**(`options`): `any` -Defined in: [src/index.ts:47](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L47) +Defined in: [src/useFirebaseStream.ts:78](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L78) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/index.ts:47](https://github.com/FirebaseExtended/reactfire/blob ## Returns `any` + +## Deprecated + +Internal option helper. Will be removed in v5.0.0. diff --git a/docs/reference/functions/checkOptions.md b/docs/reference/functions/checkOptions.md index 4f4fdaf0..e16f3c43 100644 --- a/docs/reference/functions/checkOptions.md +++ b/docs/reference/functions/checkOptions.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / checkOptions -# Function: checkOptions() +# ~~Function: checkOptions()~~ > **checkOptions**(`options`, `field`): `any` -Defined in: [src/index.ts:34](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L34) +Defined in: [src/useFirebaseStream.ts:59](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L59) ## Parameters @@ -23,3 +23,7 @@ Defined in: [src/index.ts:34](https://github.com/FirebaseExtended/reactfire/blob ## Returns `any` + +## Deprecated + +Internal option helper. Will be removed in v5.0.0. diff --git a/docs/reference/functions/checkinitialData.md b/docs/reference/functions/checkinitialData.md index f2c78405..6bc255a3 100644 --- a/docs/reference/functions/checkinitialData.md +++ b/docs/reference/functions/checkinitialData.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / checkinitialData -# Function: checkinitialData() +# ~~Function: checkinitialData()~~ > **checkinitialData**(`options`): `any` -Defined in: [src/index.ts:43](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L43) +Defined in: [src/useFirebaseStream.ts:71](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L71) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/index.ts:43](https://github.com/FirebaseExtended/reactfire/blob ## Returns `any` + +## Deprecated + +Internal option helper. Will be removed in v5.0.0. diff --git a/docs/reference/functions/preloadFirestoreDoc.md b/docs/reference/functions/preloadFirestoreDoc.md index a9c7db3a..87feee25 100644 --- a/docs/reference/functions/preloadFirestoreDoc.md +++ b/docs/reference/functions/preloadFirestoreDoc.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / preloadFirestoreDoc -# Function: preloadFirestoreDoc() +# ~~Function: preloadFirestoreDoc()~~ -> **preloadFirestoreDoc**(`refProvider`): `Promise`\<`SuspenseSubject`\<`DocumentSnapshot`\<`DocumentData`, `DocumentData`\>\>\> +> **preloadFirestoreDoc**(`refProvider`): `Promise`\<`DocumentSnapshot`\<`DocumentData`, `DocumentData`\>\> -Defined in: [src/firestore.tsx:28](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L28) +Defined in: [src/firestore.tsx:108](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L108) Preload a subscription to a Firestore document reference. @@ -22,4 +22,8 @@ Use this to warm up `useFirestoreDoc` for a specific document ## Returns -`Promise`\<`SuspenseSubject`\<`DocumentSnapshot`\<`DocumentData`, `DocumentData`\>\>\> +`Promise`\<`DocumentSnapshot`\<`DocumentData`, `DocumentData`\>\> + +## Deprecated + +Unwrap `useFirestoreDoc(ref).experimental_usable` with `React.use()` instead. Will be removed in v5.0.0. diff --git a/docs/reference/functions/preloadObservable.md b/docs/reference/functions/preloadObservable.md index 92aab4f9..0181e3da 100644 --- a/docs/reference/functions/preloadObservable.md +++ b/docs/reference/functions/preloadObservable.md @@ -4,11 +4,15 @@ [ReactFire reference docs](../README.md) / preloadObservable -# Function: preloadObservable() +# ~~Function: preloadObservable()~~ > **preloadObservable**\<`T`\>(`source`, `id`, `suspenseEnabled?`): `SuspenseSubject`\<`T`\> -Defined in: [src/useObservable.ts:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L20) +Defined in: [src/useObservable.ts:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L30) + +Starts listening to an Observable. +Call this once you know you're going to render a +child that will consume the observable. ## Type Parameters @@ -33,3 +37,7 @@ Defined in: [src/useObservable.ts:20](https://github.com/FirebaseExtended/reactf ## Returns `SuspenseSubject`\<`T`\> + +## Deprecated + +RxJS integration (`preloadObservable`, `useObservable`, `SuspenseSubject`) is deprecated and will be removed in v5.0.0. diff --git a/docs/reference/functions/preloadUser.md b/docs/reference/functions/preloadUser.md index 65e598af..2d2a2c68 100644 --- a/docs/reference/functions/preloadUser.md +++ b/docs/reference/functions/preloadUser.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / preloadUser -# Function: preloadUser() +# ~~Function: preloadUser()~~ -> **preloadUser**(`authResolver`): `Promise`\<`User` \| `null` \| `undefined`\> +> **preloadUser**(`authResolver`): `Promise`\<`User` \| `null`\> -Defined in: [src/auth.tsx:11](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L11) +Defined in: [src/auth.tsx:96](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L96) ## Parameters @@ -18,4 +18,8 @@ Defined in: [src/auth.tsx:11](https://github.com/FirebaseExtended/reactfire/blob ## Returns -`Promise`\<`User` \| `null` \| `undefined`\> +`Promise`\<`User` \| `null`\> + +## Deprecated + +Unwrap `useUser(auth).experimental_usable` with `React.use()` instead. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useAnalytics.md b/docs/reference/functions/useAnalytics.md index 1a8eb2eb..54acae8c 100644 --- a/docs/reference/functions/useAnalytics.md +++ b/docs/reference/functions/useAnalytics.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useAnalytics -# Function: useAnalytics() +# ~~Function: useAnalytics()~~ > **useAnalytics**(): `Analytics` -Defined in: [src/sdk.tsx:84](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L84) +Defined in: [src/sdk.tsx:132](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L132) ## Returns `Analytics` + +## Deprecated + +Initialize Analytics at module scope instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useAppCheck.md b/docs/reference/functions/useAppCheck.md index 0a8f8e45..1febd346 100644 --- a/docs/reference/functions/useAppCheck.md +++ b/docs/reference/functions/useAppCheck.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useAppCheck -# Function: useAppCheck() +# ~~Function: useAppCheck()~~ > **useAppCheck**(): `AppCheck` -Defined in: [src/sdk.tsx:82](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L82) +Defined in: [src/sdk.tsx:128](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L128) ## Returns `AppCheck` + +## Deprecated + +Initialize AppCheck at module scope instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useAuth.md b/docs/reference/functions/useAuth.md index fd9a7056..4e3d631f 100644 --- a/docs/reference/functions/useAuth.md +++ b/docs/reference/functions/useAuth.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useAuth -# Function: useAuth() +# ~~Function: useAuth()~~ > **useAuth**(): `Auth` -Defined in: [src/sdk.tsx:83](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L83) +Defined in: [src/sdk.tsx:130](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L130) ## Returns `Auth` + +## Deprecated + +Initialize Auth at module scope (`getAuth(app)`) instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useCallableFunctionResponse.md b/docs/reference/functions/useCallableFunctionResponse.md index 3ea2b8d8..ccf4c931 100644 --- a/docs/reference/functions/useCallableFunctionResponse.md +++ b/docs/reference/functions/useCallableFunctionResponse.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useCallableFunctionResponse -# Function: useCallableFunctionResponse() +# ~~Function: useCallableFunctionResponse()~~ > **useCallableFunctionResponse**\<`RequestData`, `ResponseData`\>(`functionName`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`ResponseData`\> -Defined in: [src/functions.tsx:14](https://github.com/FirebaseExtended/reactfire/blob/main/src/functions.tsx#L14) +Defined in: [src/functions.tsx:35](https://github.com/FirebaseExtended/reactfire/blob/main/src/functions.tsx#L35) Calls a callable function. @@ -37,3 +37,7 @@ The name of the function to call ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`ResponseData`\> + +## Deprecated + +Call `httpsCallable(functions, functionName)` directly (e.g. inside React 19 `useActionState` or an event handler). Will be removed in v5.0.0. diff --git a/docs/reference/functions/useDatabase.md b/docs/reference/functions/useDatabase.md index 1efac90b..fc81e2e5 100644 --- a/docs/reference/functions/useDatabase.md +++ b/docs/reference/functions/useDatabase.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useDatabase -# Function: useDatabase() +# ~~Function: useDatabase()~~ > **useDatabase**(): `Database` -Defined in: [src/sdk.tsx:85](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L85) +Defined in: [src/sdk.tsx:134](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L134) ## Returns `Database` + +## Deprecated + +Initialize Database at module scope (`getDatabase(app)`) instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useDatabaseList.md b/docs/reference/functions/useDatabaseList.md index 4c501bf7..e71c3979 100644 --- a/docs/reference/functions/useDatabaseList.md +++ b/docs/reference/functions/useDatabaseList.md @@ -6,9 +6,9 @@ # Function: useDatabaseList() -> **useDatabaseList**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`[] \| `QueryChange`[]\> +> **useDatabaseList**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`QueryChange`](../interfaces/QueryChange.md)[] \| `T`[]\> -Defined in: [src/database.tsx:48](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L48) +Defined in: [src/database.tsx:202](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L202) Subscribe to a Realtime Database list @@ -22,7 +22,7 @@ Subscribe to a Realtime Database list ### ref -`Query` \| `DatabaseReference` +`DatabaseReference` \| `Query` Reference to the DB List you want to listen to @@ -32,4 +32,4 @@ Reference to the DB List you want to listen to ## Returns -[`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`[] \| `QueryChange`[]\> +[`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`QueryChange`](../interfaces/QueryChange.md)[] \| `T`[]\> diff --git a/docs/reference/functions/useDatabaseListData.md b/docs/reference/functions/useDatabaseListData.md index 41d5a441..2b1c760b 100644 --- a/docs/reference/functions/useDatabaseListData.md +++ b/docs/reference/functions/useDatabaseListData.md @@ -4,11 +4,13 @@ [ReactFire reference docs](../README.md) / useDatabaseListData -# Function: useDatabaseListData() +# ~~Function: useDatabaseListData()~~ > **useDatabaseListData**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`[] \| `null`\> -Defined in: [src/database.tsx:58](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L58) +Defined in: [src/database.tsx:233](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L233) + +Subscribe to a Realtime Database list and unwrap the snapshots into an array. ## Type Parameters @@ -20,7 +22,7 @@ Defined in: [src/database.tsx:58](https://github.com/FirebaseExtended/reactfire/ ### ref -`Query` \| `DatabaseReference` +`DatabaseReference` \| `Query` ### options? @@ -29,3 +31,7 @@ Defined in: [src/database.tsx:58](https://github.com/FirebaseExtended/reactfire/ ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`[] \| `null`\> + +## Deprecated + +Use `useDatabaseList` and read `child.val()` / `child.key` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useDatabaseObject.md b/docs/reference/functions/useDatabaseObject.md index 632d5bf2..d3d340a9 100644 --- a/docs/reference/functions/useDatabaseObject.md +++ b/docs/reference/functions/useDatabaseObject.md @@ -6,9 +6,9 @@ # Function: useDatabaseObject() -> **useDatabaseObject**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `QueryChange`\> +> **useDatabaseObject**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`QueryChange`](../interfaces/QueryChange.md) \| `T`\> -Defined in: [src/database.tsx:27](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L27) +Defined in: [src/database.tsx:74](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L74) Subscribe to a Realtime Database object @@ -32,4 +32,4 @@ Reference to the DB object you want to listen to ## Returns -[`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `QueryChange`\> +[`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`QueryChange`](../interfaces/QueryChange.md) \| `T`\> diff --git a/docs/reference/functions/useDatabaseObjectData.md b/docs/reference/functions/useDatabaseObjectData.md index ae86c498..a2441131 100644 --- a/docs/reference/functions/useDatabaseObjectData.md +++ b/docs/reference/functions/useDatabaseObjectData.md @@ -4,11 +4,13 @@ [ReactFire reference docs](../README.md) / useDatabaseObjectData -# Function: useDatabaseObjectData() +# ~~Function: useDatabaseObjectData()~~ > **useDatabaseObjectData**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`\> -Defined in: [src/database.tsx:34](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L34) +Defined in: [src/database.tsx:121](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L121) + +Subscribe to a Realtime Database object and unwrap the snapshot into a plain object. ## Type Parameters @@ -29,3 +31,7 @@ Defined in: [src/database.tsx:34](https://github.com/FirebaseExtended/reactfire/ ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`\> + +## Deprecated + +Use `useDatabaseObject` and read `snapshot.val()` / `snapshot.key` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useFirebaseApp.md b/docs/reference/functions/useFirebaseApp.md index d7f1d7cc..f25980a8 100644 --- a/docs/reference/functions/useFirebaseApp.md +++ b/docs/reference/functions/useFirebaseApp.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useFirebaseApp -# Function: useFirebaseApp() +# ~~Function: useFirebaseApp()~~ > **useFirebaseApp**(): `FirebaseApp` -Defined in: [src/firebaseApp.tsx:80](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L80) +Defined in: [src/firebaseApp.tsx:99](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L99) ## Returns `FirebaseApp` + +## Deprecated + +`useFirebaseApp` is deprecated and will be removed in v5.0.0. Import your `firebaseApp` directly from the module where you called `initializeApp(config)`. diff --git a/docs/reference/functions/useFirestore.md b/docs/reference/functions/useFirestore.md index 648d679b..c27b3472 100644 --- a/docs/reference/functions/useFirestore.md +++ b/docs/reference/functions/useFirestore.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useFirestore -# Function: useFirestore() +# ~~Function: useFirestore()~~ > **useFirestore**(): `Firestore` -Defined in: [src/sdk.tsx:86](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L86) +Defined in: [src/sdk.tsx:136](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L136) ## Returns `Firestore` + +## Deprecated + +Initialize Firestore at module scope (`getFirestore(app)`) instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useFirestoreCollection.md b/docs/reference/functions/useFirestoreCollection.md index 8cc2a2e4..515bc7fd 100644 --- a/docs/reference/functions/useFirestoreCollection.md +++ b/docs/reference/functions/useFirestoreCollection.md @@ -8,7 +8,7 @@ > **useFirestoreCollection**\<`T`\>(`query`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`QuerySnapshot`\<`T`, `DocumentData`\>\> -Defined in: [src/firestore.tsx:86](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L86) +Defined in: [src/firestore.tsx:276](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L276) Subscribe to a Firestore collection diff --git a/docs/reference/functions/useFirestoreCollectionData.md b/docs/reference/functions/useFirestoreCollectionData.md index 206f7132..1ffef7d6 100644 --- a/docs/reference/functions/useFirestoreCollectionData.md +++ b/docs/reference/functions/useFirestoreCollectionData.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useFirestoreCollectionData -# Function: useFirestoreCollectionData() +# ~~Function: useFirestoreCollectionData()~~ > **useFirestoreCollectionData**\<`T`\>(`query`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`[]\> -Defined in: [src/firestore.tsx:96](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L96) +Defined in: [src/firestore.tsx:335](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L335) Subscribe to a Firestore collection and unwrap the snapshot into an array. @@ -31,3 +31,7 @@ Subscribe to a Firestore collection and unwrap the snapshot into an array. ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`[]\> + +## Deprecated + +Use `useFirestoreCollection(query)` and map `snap.docs` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useFirestoreDoc.md b/docs/reference/functions/useFirestoreDoc.md index f14bb8b4..688c77f3 100644 --- a/docs/reference/functions/useFirestoreDoc.md +++ b/docs/reference/functions/useFirestoreDoc.md @@ -8,7 +8,7 @@ > **useFirestoreDoc**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`DocumentSnapshot`\<`T`, `DocumentData`\>\> -Defined in: [src/firestore.tsx:42](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L42) +Defined in: [src/firestore.tsx:119](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L119) Subscribe to Firestore Document changes diff --git a/docs/reference/functions/useFirestoreDocData.md b/docs/reference/functions/useFirestoreDocData.md index ffaaac8b..36fd828d 100644 --- a/docs/reference/functions/useFirestoreDocData.md +++ b/docs/reference/functions/useFirestoreDocData.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useFirestoreDocData -# Function: useFirestoreDocData() +# ~~Function: useFirestoreDocData()~~ > **useFirestoreDocData**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `undefined`\> -Defined in: [src/firestore.tsx:62](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L62) +Defined in: [src/firestore.tsx:210](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L210) Subscribe to Firestore Document changes and unwrap the document into a plain object @@ -31,3 +31,7 @@ Subscribe to Firestore Document changes and unwrap the document into a plain obj ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `undefined`\> + +## Deprecated + +Use `useFirestoreDoc(ref)` and read `snap.data()` / `snap.id` / `snap.exists()` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useFirestoreDocDataOnce.md b/docs/reference/functions/useFirestoreDocDataOnce.md index 32da9e3e..5dc17368 100644 --- a/docs/reference/functions/useFirestoreDocDataOnce.md +++ b/docs/reference/functions/useFirestoreDocDataOnce.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useFirestoreDocDataOnce -# Function: useFirestoreDocDataOnce() +# ~~Function: useFirestoreDocDataOnce()~~ > **useFirestoreDocDataOnce**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `undefined`\> -Defined in: [src/firestore.tsx:74](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L74) +Defined in: [src/firestore.tsx:229](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L229) Get a Firestore document, unwrap the document into a plain object, and don't subscribe to changes @@ -31,3 +31,7 @@ Get a Firestore document, unwrap the document into a plain object, and don't sub ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `undefined`\> + +## Deprecated + +Use `useFirestoreDoc(ref, { once: true })` and read `snap.data()` / `snap.id` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useFirestoreDocOnce.md b/docs/reference/functions/useFirestoreDocOnce.md index 6280573d..5ee69d69 100644 --- a/docs/reference/functions/useFirestoreDocOnce.md +++ b/docs/reference/functions/useFirestoreDocOnce.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useFirestoreDocOnce -# Function: useFirestoreDocOnce() +# ~~Function: useFirestoreDocOnce()~~ > **useFirestoreDocOnce**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`DocumentSnapshot`\<`T`, `DocumentData`\>\> -Defined in: [src/firestore.tsx:52](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L52) +Defined in: [src/firestore.tsx:180](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L180) Get a firestore document and don't subscribe to changes @@ -31,3 +31,7 @@ Get a firestore document and don't subscribe to changes ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`DocumentSnapshot`\<`T`, `DocumentData`\>\> + +## Deprecated + +Pass `{ once: true }` to `useFirestoreDoc(ref, { once: true })` instead. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useFunctions.md b/docs/reference/functions/useFunctions.md index bd5a2ade..55ee396b 100644 --- a/docs/reference/functions/useFunctions.md +++ b/docs/reference/functions/useFunctions.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useFunctions -# Function: useFunctions() +# ~~Function: useFunctions()~~ > **useFunctions**(): `Functions` -Defined in: [src/sdk.tsx:87](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L87) +Defined in: [src/sdk.tsx:138](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L138) ## Returns `Functions` + +## Deprecated + +Initialize Functions at module scope (`getFunctions(app)`) instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useIdTokenResult.md b/docs/reference/functions/useIdTokenResult.md index 7293fc74..8382da79 100644 --- a/docs/reference/functions/useIdTokenResult.md +++ b/docs/reference/functions/useIdTokenResult.md @@ -6,24 +6,50 @@ # Function: useIdTokenResult() +## Call Signature + > **useIdTokenResult**(`user`, `forceRefresh?`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`IdTokenResult`\> -Defined in: [src/auth.tsx:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L41) +Defined in: [src/auth.tsx:173](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L173) -## Parameters +### Parameters -### user +#### user `User` -### forceRefresh? +#### forceRefresh? -`boolean` = `false` +`boolean` -### options? +#### options? [`ReactFireOptions`](../interfaces/ReactFireOptions.md)\<`IdTokenResult`\> -## Returns +### Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`IdTokenResult`\> + +## Call Signature + +> **useIdTokenResult**(`auth`, `forceRefresh?`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`IdTokenResult` \| `null`\> + +Defined in: [src/auth.tsx:178](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L178) + +### Parameters + +#### auth + +`Auth` + +#### forceRefresh? + +`boolean` + +#### options? + +[`ReactFireOptions`](../interfaces/ReactFireOptions.md)\<`IdTokenResult` \| `null`\> + +### Returns + +[`ObservableStatus`](../interfaces/ObservableStatus.md)\<`IdTokenResult` \| `null`\> diff --git a/docs/reference/functions/useIsSuspenseEnabled.md b/docs/reference/functions/useIsSuspenseEnabled.md index b068fabe..1267f9b6 100644 --- a/docs/reference/functions/useIsSuspenseEnabled.md +++ b/docs/reference/functions/useIsSuspenseEnabled.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useIsSuspenseEnabled -# Function: useIsSuspenseEnabled() +# ~~Function: useIsSuspenseEnabled()~~ > **useIsSuspenseEnabled**(): `boolean` -Defined in: [src/firebaseApp.tsx:62](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L62) +Defined in: [src/firebaseApp.tsx:75](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L75) ## Returns `boolean` + +## Deprecated + +Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of using context-based Suspense mode. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useObservable.md b/docs/reference/functions/useObservable.md index 4ca565df..57dc0934 100644 --- a/docs/reference/functions/useObservable.md +++ b/docs/reference/functions/useObservable.md @@ -4,11 +4,13 @@ [ReactFire reference docs](../README.md) / useObservable -# Function: useObservable() +# ~~Function: useObservable()~~ > **useObservable**\<`T`\>(`observableId`, `source`, `config?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`\> -Defined in: [src/useObservable.ts:67](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L67) +Defined in: [src/useObservable.ts:46](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L46) + +Subscribe to an RxJS `Observable`. ## Type Parameters @@ -33,3 +35,7 @@ Defined in: [src/useObservable.ts:67](https://github.com/FirebaseExtended/reactf ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`\> + +## Deprecated + +RxJS integration (`useObservable`, `preloadObservable`, `SuspenseSubject`) is deprecated and will be removed in v5.0.0. diff --git a/docs/reference/functions/usePerformance.md b/docs/reference/functions/usePerformance.md index 0ff1b523..853a86ef 100644 --- a/docs/reference/functions/usePerformance.md +++ b/docs/reference/functions/usePerformance.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / usePerformance -# Function: usePerformance() +# ~~Function: usePerformance()~~ > **usePerformance**(): `FirebasePerformance` -Defined in: [src/sdk.tsx:88](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L88) +Defined in: [src/sdk.tsx:140](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L140) ## Returns `FirebasePerformance` + +## Deprecated + +Initialize Performance at module scope (`getPerformance(app)`) instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useRemoteConfig.md b/docs/reference/functions/useRemoteConfig.md index 78b14879..e96a0d83 100644 --- a/docs/reference/functions/useRemoteConfig.md +++ b/docs/reference/functions/useRemoteConfig.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useRemoteConfig -# Function: useRemoteConfig() +# ~~Function: useRemoteConfig()~~ > **useRemoteConfig**(): `RemoteConfig` -Defined in: [src/sdk.tsx:90](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L90) +Defined in: [src/sdk.tsx:144](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L144) ## Returns `RemoteConfig` + +## Deprecated + +Initialize RemoteConfig at module scope (`getRemoteConfig(app)`) instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useRemoteConfigAll.md b/docs/reference/functions/useRemoteConfigAll.md index dcbdf075..185c8b91 100644 --- a/docs/reference/functions/useRemoteConfigAll.md +++ b/docs/reference/functions/useRemoteConfigAll.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useRemoteConfigAll -# Function: useRemoteConfigAll() +# ~~Function: useRemoteConfigAll()~~ -> **useRemoteConfigAll**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`AllParameters`\> +> **useRemoteConfigAll**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`AllParameters`](../type-aliases/AllParameters.md)\> -Defined in: [src/remote-config.tsx:66](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L66) +Defined in: [src/remote-config.tsx:140](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L140) Convience method similar to useRemoteConfigValue. Returns allRemote Config parameters. @@ -22,4 +22,8 @@ The parameter key in Remote Config ## Returns -[`ObservableStatus`](../interfaces/ObservableStatus.md)\<`AllParameters`\> +[`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`AllParameters`](../type-aliases/AllParameters.md)\> + +## Deprecated + +Call `fetchAndActivate(remoteConfig)` and `getAll(remoteConfig)` from `firebase/remote-config` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useRemoteConfigBoolean.md b/docs/reference/functions/useRemoteConfigBoolean.md index d696beb8..88ab8744 100644 --- a/docs/reference/functions/useRemoteConfigBoolean.md +++ b/docs/reference/functions/useRemoteConfigBoolean.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useRemoteConfigBoolean -# Function: useRemoteConfigBoolean() +# ~~Function: useRemoteConfigBoolean()~~ > **useRemoteConfigBoolean**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`boolean`\> -Defined in: [src/remote-config.tsx:58](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L58) +Defined in: [src/remote-config.tsx:130](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L130) Convience method similar to useRemoteConfigValue. Returns a `boolean` from a Remote Config parameter. @@ -23,3 +23,7 @@ The parameter key in Remote Config ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`boolean`\> + +## Deprecated + +Call `fetchAndActivate(remoteConfig)` and `getBoolean(remoteConfig, key)` from `firebase/remote-config` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useRemoteConfigNumber.md b/docs/reference/functions/useRemoteConfigNumber.md index 7affc5bb..f700dd25 100644 --- a/docs/reference/functions/useRemoteConfigNumber.md +++ b/docs/reference/functions/useRemoteConfigNumber.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useRemoteConfigNumber -# Function: useRemoteConfigNumber() +# ~~Function: useRemoteConfigNumber()~~ > **useRemoteConfigNumber**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`number`\> -Defined in: [src/remote-config.tsx:50](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L50) +Defined in: [src/remote-config.tsx:120](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L120) Convience method similar to useRemoteConfigValue. Returns a `number` from a Remote Config parameter. @@ -23,3 +23,7 @@ The parameter key in Remote Config ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`number`\> + +## Deprecated + +Call `fetchAndActivate(remoteConfig)` and `getNumber(remoteConfig, key)` from `firebase/remote-config` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useRemoteConfigString.md b/docs/reference/functions/useRemoteConfigString.md index a16940fc..f7493c82 100644 --- a/docs/reference/functions/useRemoteConfigString.md +++ b/docs/reference/functions/useRemoteConfigString.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useRemoteConfigString -# Function: useRemoteConfigString() +# ~~Function: useRemoteConfigString()~~ > **useRemoteConfigString**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`string`\> -Defined in: [src/remote-config.tsx:42](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L42) +Defined in: [src/remote-config.tsx:110](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L110) Convience method similar to useRemoteConfigValue. Returns a `string` from a Remote Config parameter. @@ -23,3 +23,7 @@ The parameter key in Remote Config ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`string`\> + +## Deprecated + +Call `fetchAndActivate(remoteConfig)` and `getString(remoteConfig, key)` from `firebase/remote-config` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useRemoteConfigValue.md b/docs/reference/functions/useRemoteConfigValue.md index f8a9c115..44af862c 100644 --- a/docs/reference/functions/useRemoteConfigValue.md +++ b/docs/reference/functions/useRemoteConfigValue.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useRemoteConfigValue -# Function: useRemoteConfigValue() +# ~~Function: useRemoteConfigValue()~~ > **useRemoteConfigValue**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`Value`\> -Defined in: [src/remote-config.tsx:34](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L34) +Defined in: [src/remote-config.tsx:100](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L100) Accepts a key and optionally a Remote Config instance. Returns a Remote Config Value. @@ -24,3 +24,7 @@ The parameter key in Remote Config ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`Value`\> + +## Deprecated + +Call `fetchAndActivate(remoteConfig)` and synchronous getters (`getValue(remoteConfig, key)`) from `firebase/remote-config` directly. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useSigninCheck.md b/docs/reference/functions/useSigninCheck.md index 895405bc..0d479acd 100644 --- a/docs/reference/functions/useSigninCheck.md +++ b/docs/reference/functions/useSigninCheck.md @@ -4,42 +4,14 @@ [ReactFire reference docs](../README.md) / useSigninCheck -# Function: useSigninCheck() +# ~~Function: useSigninCheck()~~ > **useSigninCheck**(`options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`SigninCheckResult`](../type-aliases/SigninCheckResult.md)\> -Defined in: [src/auth.tsx:136](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L136) +Defined in: [src/auth.tsx:343](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L343) Subscribe to the signed-in status of a user. -```ts -const { status, data:signInCheckResult } = useSigninCheck(); - -if (status === 'loading') { - return } - -if (signInCheckResult.signedIn === true) { - return -} else { - return -} -``` - -Optionally check [custom claims](https://firebase.google.com/docs/auth/admin/custom-claims) of a user as well. - -```ts -// pass in an object describing the custom claims a user must have -const {status, data: signInCheckResult} = useSigninCheck({requiredClaims: {admin: true}}); - -// pass in a custom claims validator function -const {status, data: signInCheckResult} = useSigninCheck({validateCustomClaims: (userClaims) => { - // custom validation logic... -}}); - -// You can optionally force-refresh the token -const {status, data: signInCheckResult} = useSigninCheck({forceRefresh: true, requiredClaims: {admin: true}}); -``` - ## Parameters ### options? @@ -49,3 +21,7 @@ const {status, data: signInCheckResult} = useSigninCheck({forceRefresh: true, re ## Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`SigninCheckResult`](../type-aliases/SigninCheckResult.md)\> + +## Deprecated + +Use `useUser(auth)`, `useIdTokenResult(auth)`, or `` instead. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useStorage.md b/docs/reference/functions/useStorage.md index 74d28fe2..dcb593f8 100644 --- a/docs/reference/functions/useStorage.md +++ b/docs/reference/functions/useStorage.md @@ -4,12 +4,16 @@ [ReactFire reference docs](../README.md) / useStorage -# Function: useStorage() +# ~~Function: useStorage()~~ > **useStorage**(): `FirebaseStorage` -Defined in: [src/sdk.tsx:89](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L89) +Defined in: [src/sdk.tsx:142](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L142) ## Returns `FirebaseStorage` + +## Deprecated + +Initialize Storage at module scope (`getStorage(app)`) instead of reading from Context. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useStorageDownloadURL.md b/docs/reference/functions/useStorageDownloadURL.md index ced03bbf..4e2a3a9b 100644 --- a/docs/reference/functions/useStorageDownloadURL.md +++ b/docs/reference/functions/useStorageDownloadURL.md @@ -6,9 +6,9 @@ # Function: useStorageDownloadURL() -> **useStorageDownloadURL**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`string` \| `T`\> +> **useStorageDownloadURL**\<`T`\>(`storageRef`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`string` \| `T`\> -Defined in: [src/storage.tsx:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L30) +Defined in: [src/storage.tsx:134](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L134) Subscribe to a storage ref's download URL @@ -20,12 +20,10 @@ Subscribe to a storage ref's download URL ## Parameters -### ref +### storageRef `StorageReference` -reference to the blob you want to download - ### options? [`ReactFireOptions`](../interfaces/ReactFireOptions.md)\<`T`\> diff --git a/docs/reference/functions/useStorageTask.md b/docs/reference/functions/useStorageTask.md index f6cbc13d..442f780b 100644 --- a/docs/reference/functions/useStorageTask.md +++ b/docs/reference/functions/useStorageTask.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useStorageTask -# Function: useStorageTask() +# ~~Function: useStorageTask()~~ -> **useStorageTask**\<`T`\>(`task`, `ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `UploadTaskSnapshot`\> +> **useStorageTask**\<`T`\>(`task`, `ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`UploadTaskSnapshot` \| `T`\> -Defined in: [src/storage.tsx:17](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L17) +Defined in: [src/storage.tsx:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L64) Subscribe to the progress of a storage task @@ -38,4 +38,8 @@ reference to the blob the task is acting on ## Returns -[`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `UploadTaskSnapshot`\> +[`ObservableStatus`](../interfaces/ObservableStatus.md)\<`UploadTaskSnapshot` \| `T`\> + +## Deprecated + +Track `uploadBytesResumable` progress inside your upload handler or action instead. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useSuspenseEnabledFromConfigAndContext.md b/docs/reference/functions/useSuspenseEnabledFromConfigAndContext.md index 4781e595..e670592f 100644 --- a/docs/reference/functions/useSuspenseEnabledFromConfigAndContext.md +++ b/docs/reference/functions/useSuspenseEnabledFromConfigAndContext.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / useSuspenseEnabledFromConfigAndContext -# Function: useSuspenseEnabledFromConfigAndContext() +# ~~Function: useSuspenseEnabledFromConfigAndContext()~~ > **useSuspenseEnabledFromConfigAndContext**(`suspenseFromConfig?`): `boolean` -Defined in: [src/firebaseApp.tsx:69](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L69) +Defined in: [src/firebaseApp.tsx:85](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L85) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/firebaseApp.tsx:69](https://github.com/FirebaseExtended/reactfi ## Returns `boolean` + +## Deprecated + +Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of using context-based Suspense mode. Will be removed in v5.0.0. diff --git a/docs/reference/functions/useUser.md b/docs/reference/functions/useUser.md index 0f7277e6..6c36eee3 100644 --- a/docs/reference/functions/useUser.md +++ b/docs/reference/functions/useUser.md @@ -6,24 +6,54 @@ # Function: useUser() +## Call Signature + > **useUser**\<`T`\>(`options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`User` \| `null`\> -Defined in: [src/auth.tsx:22](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L22) +Defined in: [src/auth.tsx:108](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L108) Subscribe to Firebase auth state changes, including token refresh -## Type Parameters +### Type Parameters -### T +#### T `T` = `unknown` -## Parameters +### Parameters + +#### options? + +[`ReactFireOptions`](../interfaces/ReactFireOptions.md)\<`T`\> + +### Returns + +[`ObservableStatus`](../interfaces/ObservableStatus.md)\<`User` \| `null`\> + +## Call Signature + +> **useUser**\<`T`\>(`auth`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`User` \| `null`\> + +Defined in: [src/auth.tsx:109](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L109) + +Subscribe to Firebase auth state changes, including token refresh + +### Type Parameters + +#### T + +`T` = `unknown` + +### Parameters + +#### auth + +`Auth` -### options? +#### options? [`ReactFireOptions`](../interfaces/ReactFireOptions.md)\<`T`\> -## Returns +### Returns [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`User` \| `null`\> diff --git a/docs/reference/interfaces/AuthCheckProps.md b/docs/reference/interfaces/AuthCheckProps.md index c5edcb62..876ade53 100644 --- a/docs/reference/interfaces/AuthCheckProps.md +++ b/docs/reference/interfaces/AuthCheckProps.md @@ -6,15 +6,23 @@ # Interface: AuthCheckProps -Defined in: [src/auth.tsx:54](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L54) +Defined in: [src/auth.tsx:264](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L264) ## Properties +### auth? + +> `optional` **auth?**: `Auth` + +Defined in: [src/auth.tsx:265](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L265) + +*** + ### children > **children**: `ReactNode` -Defined in: [src/auth.tsx:56](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L56) +Defined in: [src/auth.tsx:267](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L267) *** @@ -22,7 +30,7 @@ Defined in: [src/auth.tsx:56](https://github.com/FirebaseExtended/reactfire/blob > **fallback**: `ReactNode` -Defined in: [src/auth.tsx:55](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L55) +Defined in: [src/auth.tsx:266](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L266) *** @@ -30,4 +38,4 @@ Defined in: [src/auth.tsx:55](https://github.com/FirebaseExtended/reactfire/blob > `optional` **requiredClaims?**: `Object` -Defined in: [src/auth.tsx:57](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L57) +Defined in: [src/auth.tsx:268](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L268) diff --git a/docs/reference/interfaces/ClaimCheckErrors.md b/docs/reference/interfaces/ClaimCheckErrors.md index ce0afa98..65d0fcb3 100644 --- a/docs/reference/interfaces/ClaimCheckErrors.md +++ b/docs/reference/interfaces/ClaimCheckErrors.md @@ -4,9 +4,13 @@ [ReactFire reference docs](../README.md) / ClaimCheckErrors -# Interface: ClaimCheckErrors +# ~~Interface: ClaimCheckErrors~~ -Defined in: [src/auth.tsx:67](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L67) +Defined in: [src/auth.tsx:284](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L284) + +## Deprecated + +Will be removed in v5.0.0 along with `useSigninCheck`. ## Indexable diff --git a/docs/reference/interfaces/ClaimsCheckProps.md b/docs/reference/interfaces/ClaimsCheckProps.md index 34375923..36d2d5c2 100644 --- a/docs/reference/interfaces/ClaimsCheckProps.md +++ b/docs/reference/interfaces/ClaimsCheckProps.md @@ -4,33 +4,37 @@ [ReactFire reference docs](../README.md) / ClaimsCheckProps -# Interface: ClaimsCheckProps +# ~~Interface: ClaimsCheckProps~~ -Defined in: [src/auth.tsx:60](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L60) +Defined in: [src/auth.tsx:274](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L274) + +## Deprecated + +Use `` or `useIdTokenResult(auth)` instead. Will be removed in v5.0.0. ## Properties -### children +### ~~children~~ > **children**: `ReactNode` -Defined in: [src/auth.tsx:63](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L63) +Defined in: [src/auth.tsx:277](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L277) *** -### fallback +### ~~fallback~~ > **fallback**: `ReactNode` -Defined in: [src/auth.tsx:62](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L62) +Defined in: [src/auth.tsx:276](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L276) *** -### requiredClaims +### ~~requiredClaims~~ > **requiredClaims**: `object` -Defined in: [src/auth.tsx:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L64) +Defined in: [src/auth.tsx:278](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L278) #### Index Signature @@ -38,8 +42,8 @@ Defined in: [src/auth.tsx:64](https://github.com/FirebaseExtended/reactfire/blob *** -### user +### ~~user~~ > **user**: `User` -Defined in: [src/auth.tsx:61](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L61) +Defined in: [src/auth.tsx:275](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L275) diff --git a/docs/reference/interfaces/ClaimsValidator.md b/docs/reference/interfaces/ClaimsValidator.md index 436ddd45..f532d3d9 100644 --- a/docs/reference/interfaces/ClaimsValidator.md +++ b/docs/reference/interfaces/ClaimsValidator.md @@ -4,13 +4,17 @@ [ReactFire reference docs](../README.md) / ClaimsValidator -# Interface: ClaimsValidator() +# ~~Interface: ClaimsValidator()~~ -Defined in: [src/auth.tsx:93](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L93) +Defined in: [src/auth.tsx:322](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L322) + +## Deprecated + +Will be removed in v5.0.0 along with `useSigninCheck`. > **ClaimsValidator**(`claims`): `object` -Defined in: [src/auth.tsx:94](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L94) +Defined in: [src/auth.tsx:323](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L323) ## Parameters @@ -22,10 +26,14 @@ Defined in: [src/auth.tsx:94](https://github.com/FirebaseExtended/reactfire/blob `object` -### errors +### ~~errors~~ > **errors**: \{ \} \| [`ClaimCheckErrors`](ClaimCheckErrors.md) -### hasRequiredClaims +### ~~hasRequiredClaims~~ > **hasRequiredClaims**: `boolean` + +## Deprecated + +Will be removed in v5.0.0 along with `useSigninCheck`. diff --git a/docs/reference/interfaces/FirebaseAppProviderProps.md b/docs/reference/interfaces/FirebaseAppProviderProps.md index 80a7ff39..02c2c5da 100644 --- a/docs/reference/interfaces/FirebaseAppProviderProps.md +++ b/docs/reference/interfaces/FirebaseAppProviderProps.md @@ -4,38 +4,46 @@ [ReactFire reference docs](../README.md) / FirebaseAppProviderProps -# Interface: FirebaseAppProviderProps +# ~~Interface: FirebaseAppProviderProps~~ -Defined in: [src/firebaseApp.tsx:14](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L14) +Defined in: [src/firebaseApp.tsx:17](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L17) + +## Deprecated + +`` is deprecated and will be removed in v5.0.0. Initialize your Firebase app at module scope (`export const app = initializeApp(config)`) and pass SDK instances or references directly to ReactFire hooks. ## Properties -### appName? +### ~~appName?~~ > `optional` **appName?**: `string` -Defined in: [src/firebaseApp.tsx:17](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L17) +Defined in: [src/firebaseApp.tsx:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L20) *** -### firebaseApp? +### ~~firebaseApp?~~ > `optional` **firebaseApp?**: `FirebaseApp` -Defined in: [src/firebaseApp.tsx:15](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L15) +Defined in: [src/firebaseApp.tsx:18](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L18) *** -### firebaseConfig? +### ~~firebaseConfig?~~ > `optional` **firebaseConfig?**: `FirebaseOptions` -Defined in: [src/firebaseApp.tsx:16](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L16) +Defined in: [src/firebaseApp.tsx:19](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L19) *** -### suspense? +### ~~suspense?~~ > `optional` **suspense?**: `boolean` -Defined in: [src/firebaseApp.tsx:18](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L18) +Defined in: [src/firebaseApp.tsx:24](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L24) + +#### Deprecated + +Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of setting `suspense={true}`. diff --git a/docs/reference/interfaces/ObservableStatus.md b/docs/reference/interfaces/ObservableStatus.md index a1846ef9..b260cf40 100644 --- a/docs/reference/interfaces/ObservableStatus.md +++ b/docs/reference/interfaces/ObservableStatus.md @@ -6,7 +6,7 @@ # Interface: ObservableStatus\ -Defined in: [src/useObservable.ts:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L30) +Defined in: [src/useFirebaseStream.ts:82](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L82) ## Type Parameters @@ -20,11 +20,11 @@ Defined in: [src/useObservable.ts:30](https://github.com/FirebaseExtended/reactf > **data**: `T` -Defined in: [src/useObservable.ts:56](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L56) +Defined in: [src/useFirebaseStream.ts:112](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L112) The most recent value. -If `initialData` is passed in, the first value of `data` will be the valuea provided in `initialData` **UNLESS** the underlying observable is ready, in which case it will skip `initialData`. +If `initialData` is passed in, the first value of `data` will be the value provided in `initialData` **UNLESS** the underlying observable is ready, in which case it will skip `initialData`. *** @@ -32,49 +32,75 @@ If `initialData` is passed in, the first value of `data` will be the valuea prov > **error**: `Error` \| `undefined` -Defined in: [src/useObservable.ts:60](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L60) +Defined in: [src/useFirebaseStream.ts:116](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L116) Any error that may have occurred in the underlying observable *** -### firstValuePromise +### experimental\_usable + +> `readonly` **experimental\_usable**: [`ReactFireUsable`](../type-aliases/ReactFireUsable.md)\<`T`\> + +Defined in: [src/useFirebaseStream.ts:138](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L138) + +A React 19 `Usable` that can be passed directly into `React.use()` +(`const data = use(useFirestoreDoc(ref).experimental_usable)`). + +Suspends on initial load when no data is available yet, and synchronously +unwraps the latest live value on all subsequent renders without re-suspending. + +*** + +### ~~firstValuePromise~~ > **firstValuePromise**: `Promise`\<`void`\> -Defined in: [src/useObservable.ts:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L64) +Defined in: [src/useFirebaseStream.ts:122](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L122) Promise that resolves after first emit from observable +#### Deprecated + +Pass `.experimental_usable` (or `.usable`) to `React.use()` instead. Will be removed in v5.0.0. + *** -### hasEmitted +### ~~hasEmitted~~ > **hasEmitted**: `boolean` -Defined in: [src/useObservable.ts:46](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L46) +Defined in: [src/useFirebaseStream.ts:100](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L100) Indicates whether the hook has emitted a value at some point If `initialData` is passed in, this will be `true`. +#### Deprecated + +Check `status === 'success'` or pass `.experimental_usable` to `React.use()` instead. Will be removed in v5.0.0. + *** -### isComplete +### ~~isComplete~~ > **isComplete**: `boolean` -Defined in: [src/useObservable.ts:50](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L50) +Defined in: [src/useFirebaseStream.ts:106](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L106) If this is `true`, the hook will be emitting no further items. +#### Deprecated + +Will be removed in v5.0.0. + *** ### status -> **status**: `"error"` \| `"loading"` \| `"success"` +> **status**: `"loading"` \| `"error"` \| `"success"` -Defined in: [src/useObservable.ts:40](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L40) +Defined in: [src/useFirebaseStream.ts:92](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L92) The loading status. @@ -83,3 +109,17 @@ The loading status. - `success`: The hook has emitted at least one value If `initialData` is passed in, this will skip `loading` and go straight to `success`. + +*** + +### usable + +> `readonly` **usable**: [`ReactFireUsable`](../type-aliases/ReactFireUsable.md)\<`T`\> + +Defined in: [src/useFirebaseStream.ts:130](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L130) + +A React 19 `Usable` that can be passed directly into `React.use()` +(`const data = use(useFirestoreDoc(ref).usable)`). + +Suspends on initial load when no data is available yet, and synchronously +unwraps the latest live value on all subsequent renders without re-suspending. diff --git a/docs/reference/interfaces/QueryChange.md b/docs/reference/interfaces/QueryChange.md new file mode 100644 index 00000000..27ce86b1 --- /dev/null +++ b/docs/reference/interfaces/QueryChange.md @@ -0,0 +1,37 @@ +[**ReactFire reference docs**](../README.md) + +*** + +[ReactFire reference docs](../README.md) / QueryChange + +# ~~Interface: QueryChange~~ + +Defined in: [src/database.tsx:27](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L27) + +## Deprecated + +Legacy rxfire change wrapper. In v5.0.0, `useDatabaseObject` returns `DataSnapshot` and `useDatabaseList` returns `DataSnapshot[]`. + +## Properties + +### ~~event~~ + +> **event**: [`ListenEvent`](../enumerations/ListenEvent.md) + +Defined in: [src/database.tsx:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L30) + +*** + +### ~~prevKey~~ + +> **prevKey**: `string` \| `null` \| `undefined` + +Defined in: [src/database.tsx:29](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L29) + +*** + +### ~~snapshot~~ + +> **snapshot**: `DataSnapshot` + +Defined in: [src/database.tsx:28](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L28) diff --git a/docs/reference/interfaces/ReactFireOptions.md b/docs/reference/interfaces/ReactFireOptions.md index 7ca63c5e..bba0f2fe 100644 --- a/docs/reference/interfaces/ReactFireOptions.md +++ b/docs/reference/interfaces/ReactFireOptions.md @@ -6,7 +6,7 @@ # Interface: ReactFireOptions\ -Defined in: [src/index.ts:24](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L24) +Defined in: [src/useFirebaseStream.ts:32](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L32) ## Extended by @@ -20,11 +20,25 @@ Defined in: [src/index.ts:24](https://github.com/FirebaseExtended/reactfire/blob ## Properties -### idField? +### ~~idField?~~ > `optional` **idField?**: `string` -Defined in: [src/index.ts:25](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L25) +Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) + +#### Deprecated + +`*Data` hooks and `idField` are deprecated and will be removed in v5.0.0. Use snapshot hooks (`useFirestoreDoc`, `useFirestoreCollection`, `useDatabaseObject`, `useDatabaseList`) and read `doc.id` or `child.key` directly. + +*** + +### includeMetadataChanges? + +> `optional` **includeMetadataChanges?**: `boolean` + +Defined in: [src/useFirebaseStream.ts:53](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L53) + +Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). *** @@ -32,7 +46,17 @@ Defined in: [src/index.ts:25](https://github.com/FirebaseExtended/reactfire/blob > `optional` **initialData?**: `any` -Defined in: [src/index.ts:26](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L26) +Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L37) + +*** + +### once? + +> `optional` **once?**: `boolean` + +Defined in: [src/useFirebaseStream.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L49) + +Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). *** @@ -40,16 +64,20 @@ Defined in: [src/index.ts:26](https://github.com/FirebaseExtended/reactfire/blob > `optional` **startWithValue?**: `any` -Defined in: [src/index.ts:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L30) +Defined in: [src/useFirebaseStream.ts:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L41) #### Deprecated -use initialData instead +use `initialData` instead *** -### suspense? +### ~~suspense?~~ > `optional` **suspense?**: `boolean` -Defined in: [src/index.ts:31](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L31) +Defined in: [src/useFirebaseStream.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L45) + +#### Deprecated + +Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of setting `suspense: true`. diff --git a/docs/reference/interfaces/SignInCheckOptionsBasic.md b/docs/reference/interfaces/SignInCheckOptionsBasic.md index 66967bf8..84b2b699 100644 --- a/docs/reference/interfaces/SignInCheckOptionsBasic.md +++ b/docs/reference/interfaces/SignInCheckOptionsBasic.md @@ -4,9 +4,13 @@ [ReactFire reference docs](../README.md) / SignInCheckOptionsBasic -# Interface: SignInCheckOptionsBasic +# ~~Interface: SignInCheckOptionsBasic~~ -Defined in: [src/auth.tsx:85](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L85) +Defined in: [src/auth.tsx:308](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L308) + +## Deprecated + +Will be removed in v5.0.0 along with `useSigninCheck`. ## Extends @@ -19,19 +23,23 @@ Defined in: [src/auth.tsx:85](https://github.com/FirebaseExtended/reactfire/blob ## Properties -### forceRefresh? +### ~~forceRefresh?~~ > `optional` **forceRefresh?**: `boolean` -Defined in: [src/auth.tsx:86](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L86) +Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L309) *** -### idField? +### ~~idField?~~ > `optional` **idField?**: `string` -Defined in: [src/index.ts:25](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L25) +Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) + +#### Deprecated + +`*Data` hooks and `idField` are deprecated and will be removed in v5.0.0. Use snapshot hooks (`useFirestoreDoc`, `useFirestoreCollection`, `useDatabaseObject`, `useDatabaseList`) and read `doc.id` or `child.key` directly. #### Inherited from @@ -39,11 +47,25 @@ Defined in: [src/index.ts:25](https://github.com/FirebaseExtended/reactfire/blob *** -### initialData? +### ~~includeMetadataChanges?~~ + +> `optional` **includeMetadataChanges?**: `boolean` + +Defined in: [src/useFirebaseStream.ts:53](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L53) + +Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). + +#### Inherited from + +[`ReactFireOptions`](ReactFireOptions.md).[`includeMetadataChanges`](ReactFireOptions.md#includemetadatachanges) + +*** + +### ~~initialData?~~ > `optional` **initialData?**: `any` -Defined in: [src/index.ts:26](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L26) +Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L37) #### Inherited from @@ -51,15 +73,29 @@ Defined in: [src/index.ts:26](https://github.com/FirebaseExtended/reactfire/blob *** +### ~~once?~~ + +> `optional` **once?**: `boolean` + +Defined in: [src/useFirebaseStream.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L49) + +Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). + +#### Inherited from + +[`ReactFireOptions`](ReactFireOptions.md).[`once`](ReactFireOptions.md#once) + +*** + ### ~~startWithValue?~~ > `optional` **startWithValue?**: `any` -Defined in: [src/index.ts:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L30) +Defined in: [src/useFirebaseStream.ts:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L41) #### Deprecated -use initialData instead +use `initialData` instead #### Inherited from @@ -67,11 +103,15 @@ use initialData instead *** -### suspense? +### ~~suspense?~~ > `optional` **suspense?**: `boolean` -Defined in: [src/index.ts:31](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L31) +Defined in: [src/useFirebaseStream.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L45) + +#### Deprecated + +Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of setting `suspense: true`. #### Inherited from diff --git a/docs/reference/interfaces/SignInCheckOptionsClaimsObject.md b/docs/reference/interfaces/SignInCheckOptionsClaimsObject.md index 09a1eae0..fc8b606e 100644 --- a/docs/reference/interfaces/SignInCheckOptionsClaimsObject.md +++ b/docs/reference/interfaces/SignInCheckOptionsClaimsObject.md @@ -4,9 +4,13 @@ [ReactFire reference docs](../README.md) / SignInCheckOptionsClaimsObject -# Interface: SignInCheckOptionsClaimsObject +# ~~Interface: SignInCheckOptionsClaimsObject~~ -Defined in: [src/auth.tsx:89](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L89) +Defined in: [src/auth.tsx:315](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L315) + +## Deprecated + +Will be removed in v5.0.0 along with `useSigninCheck`. ## Extends @@ -14,11 +18,11 @@ Defined in: [src/auth.tsx:89](https://github.com/FirebaseExtended/reactfire/blob ## Properties -### forceRefresh? +### ~~forceRefresh?~~ > `optional` **forceRefresh?**: `boolean` -Defined in: [src/auth.tsx:86](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L86) +Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L309) #### Inherited from @@ -26,11 +30,15 @@ Defined in: [src/auth.tsx:86](https://github.com/FirebaseExtended/reactfire/blob *** -### idField? +### ~~idField?~~ > `optional` **idField?**: `string` -Defined in: [src/index.ts:25](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L25) +Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) + +#### Deprecated + +`*Data` hooks and `idField` are deprecated and will be removed in v5.0.0. Use snapshot hooks (`useFirestoreDoc`, `useFirestoreCollection`, `useDatabaseObject`, `useDatabaseList`) and read `doc.id` or `child.key` directly. #### Inherited from @@ -38,11 +46,25 @@ Defined in: [src/index.ts:25](https://github.com/FirebaseExtended/reactfire/blob *** -### initialData? +### ~~includeMetadataChanges?~~ + +> `optional` **includeMetadataChanges?**: `boolean` + +Defined in: [src/useFirebaseStream.ts:53](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L53) + +Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). + +#### Inherited from + +[`SignInCheckOptionsBasic`](SignInCheckOptionsBasic.md).[`includeMetadataChanges`](SignInCheckOptionsBasic.md#includemetadatachanges) + +*** + +### ~~initialData?~~ > `optional` **initialData?**: `any` -Defined in: [src/index.ts:26](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L26) +Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L37) #### Inherited from @@ -50,11 +72,25 @@ Defined in: [src/index.ts:26](https://github.com/FirebaseExtended/reactfire/blob *** -### requiredClaims +### ~~once?~~ + +> `optional` **once?**: `boolean` + +Defined in: [src/useFirebaseStream.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L49) + +Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). + +#### Inherited from + +[`SignInCheckOptionsBasic`](SignInCheckOptionsBasic.md).[`once`](SignInCheckOptionsBasic.md#once) + +*** + +### ~~requiredClaims~~ > **requiredClaims**: `ParsedToken` -Defined in: [src/auth.tsx:90](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L90) +Defined in: [src/auth.tsx:316](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L316) *** @@ -62,11 +98,11 @@ Defined in: [src/auth.tsx:90](https://github.com/FirebaseExtended/reactfire/blob > `optional` **startWithValue?**: `any` -Defined in: [src/index.ts:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L30) +Defined in: [src/useFirebaseStream.ts:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L41) #### Deprecated -use initialData instead +use `initialData` instead #### Inherited from @@ -74,11 +110,15 @@ use initialData instead *** -### suspense? +### ~~suspense?~~ > `optional` **suspense?**: `boolean` -Defined in: [src/index.ts:31](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L31) +Defined in: [src/useFirebaseStream.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L45) + +#### Deprecated + +Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of setting `suspense: true`. #### Inherited from diff --git a/docs/reference/interfaces/SignInCheckOptionsClaimsValidator.md b/docs/reference/interfaces/SignInCheckOptionsClaimsValidator.md index 5ba7f609..39585c22 100644 --- a/docs/reference/interfaces/SignInCheckOptionsClaimsValidator.md +++ b/docs/reference/interfaces/SignInCheckOptionsClaimsValidator.md @@ -4,9 +4,13 @@ [ReactFire reference docs](../README.md) / SignInCheckOptionsClaimsValidator -# Interface: SignInCheckOptionsClaimsValidator +# ~~Interface: SignInCheckOptionsClaimsValidator~~ -Defined in: [src/auth.tsx:100](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L100) +Defined in: [src/auth.tsx:332](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L332) + +## Deprecated + +Will be removed in v5.0.0 along with `useSigninCheck`. ## Extends @@ -14,11 +18,11 @@ Defined in: [src/auth.tsx:100](https://github.com/FirebaseExtended/reactfire/blo ## Properties -### forceRefresh? +### ~~forceRefresh?~~ > `optional` **forceRefresh?**: `boolean` -Defined in: [src/auth.tsx:86](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L86) +Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L309) #### Inherited from @@ -26,11 +30,15 @@ Defined in: [src/auth.tsx:86](https://github.com/FirebaseExtended/reactfire/blob *** -### idField? +### ~~idField?~~ > `optional` **idField?**: `string` -Defined in: [src/index.ts:25](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L25) +Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) + +#### Deprecated + +`*Data` hooks and `idField` are deprecated and will be removed in v5.0.0. Use snapshot hooks (`useFirestoreDoc`, `useFirestoreCollection`, `useDatabaseObject`, `useDatabaseList`) and read `doc.id` or `child.key` directly. #### Inherited from @@ -38,11 +46,25 @@ Defined in: [src/index.ts:25](https://github.com/FirebaseExtended/reactfire/blob *** -### initialData? +### ~~includeMetadataChanges?~~ + +> `optional` **includeMetadataChanges?**: `boolean` + +Defined in: [src/useFirebaseStream.ts:53](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L53) + +Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). + +#### Inherited from + +[`SignInCheckOptionsBasic`](SignInCheckOptionsBasic.md).[`includeMetadataChanges`](SignInCheckOptionsBasic.md#includemetadatachanges) + +*** + +### ~~initialData?~~ > `optional` **initialData?**: `any` -Defined in: [src/index.ts:26](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L26) +Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L37) #### Inherited from @@ -50,15 +72,29 @@ Defined in: [src/index.ts:26](https://github.com/FirebaseExtended/reactfire/blob *** +### ~~once?~~ + +> `optional` **once?**: `boolean` + +Defined in: [src/useFirebaseStream.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L49) + +Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). + +#### Inherited from + +[`SignInCheckOptionsBasic`](SignInCheckOptionsBasic.md).[`once`](SignInCheckOptionsBasic.md#once) + +*** + ### ~~startWithValue?~~ > `optional` **startWithValue?**: `any` -Defined in: [src/index.ts:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L30) +Defined in: [src/useFirebaseStream.ts:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L41) #### Deprecated -use initialData instead +use `initialData` instead #### Inherited from @@ -66,11 +102,15 @@ use initialData instead *** -### suspense? +### ~~suspense?~~ > `optional` **suspense?**: `boolean` -Defined in: [src/index.ts:31](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L31) +Defined in: [src/useFirebaseStream.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L45) + +#### Deprecated + +Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of setting `suspense: true`. #### Inherited from @@ -78,8 +118,8 @@ Defined in: [src/index.ts:31](https://github.com/FirebaseExtended/reactfire/blob *** -### validateCustomClaims +### ~~validateCustomClaims~~ > **validateCustomClaims**: [`ClaimsValidator`](ClaimsValidator.md) -Defined in: [src/auth.tsx:101](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L101) +Defined in: [src/auth.tsx:333](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L333) diff --git a/docs/reference/interfaces/SuspensePerfProps.md b/docs/reference/interfaces/SuspensePerfProps.md index a2ab17da..1a28cdb7 100644 --- a/docs/reference/interfaces/SuspensePerfProps.md +++ b/docs/reference/interfaces/SuspensePerfProps.md @@ -6,7 +6,7 @@ # Interface: SuspensePerfProps -Defined in: [src/performance.tsx:3](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L3) +Defined in: [src/performance.tsx:6](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L6) ## Properties @@ -14,7 +14,7 @@ Defined in: [src/performance.tsx:3](https://github.com/FirebaseExtended/reactfir > **children**: `ReactNode` -Defined in: [src/performance.tsx:4](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L4) +Defined in: [src/performance.tsx:7](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L7) *** @@ -22,7 +22,17 @@ Defined in: [src/performance.tsx:4](https://github.com/FirebaseExtended/reactfir > **fallback**: `ReactNode` -Defined in: [src/performance.tsx:6](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L6) +Defined in: [src/performance.tsx:9](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L9) + +*** + +### perf? + +> `optional` **perf?**: `FirebasePerformance` + +Defined in: [src/performance.tsx:13](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L13) + +Optional Firebase Performance Monitoring instance to record the trace with. *** @@ -30,4 +40,4 @@ Defined in: [src/performance.tsx:6](https://github.com/FirebaseExtended/reactfir > **traceId**: `string` -Defined in: [src/performance.tsx:5](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L5) +Defined in: [src/performance.tsx:8](https://github.com/FirebaseExtended/reactfire/blob/main/src/performance.tsx#L8) diff --git a/docs/reference/type-aliases/AllParameters.md b/docs/reference/type-aliases/AllParameters.md new file mode 100644 index 00000000..74790a9f --- /dev/null +++ b/docs/reference/type-aliases/AllParameters.md @@ -0,0 +1,19 @@ +[**ReactFire reference docs**](../README.md) + +*** + +[ReactFire reference docs](../README.md) / AllParameters + +# ~~Type Alias: AllParameters~~ + +> **AllParameters** = `object` + +Defined in: [src/remote-config.tsx:17](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L17) + +## Index Signature + +\[`key`: `string`\]: `Value` + +## Deprecated + +Use `getAll(remoteConfig)` from `firebase/remote-config` directly. Will be removed in v5.0.0. diff --git a/docs/reference/type-aliases/ReactFireGlobals.md b/docs/reference/type-aliases/ReactFireGlobals.md index 995ef49a..defaf4eb 100644 --- a/docs/reference/type-aliases/ReactFireGlobals.md +++ b/docs/reference/type-aliases/ReactFireGlobals.md @@ -4,32 +4,36 @@ [ReactFire reference docs](../README.md) / ReactFireGlobals -# Type Alias: ReactFireGlobals +# ~~Type Alias: ReactFireGlobals~~ > **ReactFireGlobals** = `object` -Defined in: [src/index.ts:6](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L6) +Defined in: [src/index.ts:9](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L9) + +## Deprecated + +Internal global cache type. Will be removed in v5.0.0. ## Properties -### \_reactFireDatabaseCachedQueries +### ~~\_reactFireDatabaseCachedQueries~~ > **\_reactFireDatabaseCachedQueries**: `DatabaseQuery`[] -Defined in: [src/index.ts:7](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L7) +Defined in: [src/index.ts:10](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L10) *** -### \_reactFireFirestoreQueryCache +### ~~\_reactFireFirestoreQueryCache~~ > **\_reactFireFirestoreQueryCache**: `FirestoreQuery`[] -Defined in: [src/index.ts:8](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L8) +Defined in: [src/index.ts:11](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L11) *** -### \_reactFirePreloadedObservables +### ~~\_reactFirePreloadedObservables~~ > **\_reactFirePreloadedObservables**: `Map`\<`string`, `SuspenseSubject`\<`any`\>\> -Defined in: [src/index.ts:9](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L9) +Defined in: [src/index.ts:12](https://github.com/FirebaseExtended/reactfire/blob/main/src/index.ts#L12) diff --git a/docs/reference/type-aliases/ReactFireUsable.md b/docs/reference/type-aliases/ReactFireUsable.md new file mode 100644 index 00000000..8b24c337 --- /dev/null +++ b/docs/reference/type-aliases/ReactFireUsable.md @@ -0,0 +1,20 @@ +[**ReactFire reference docs**](../README.md) + +*** + +[ReactFire reference docs](../README.md) / ReactFireUsable + +# Type Alias: ReactFireUsable\ + +> **ReactFireUsable**\<`T`\> = `Promise`\<`T`\> + +Defined in: [src/useFirebaseStream.ts:9](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L9) + +A thenable compatible with React 19's `React.use()` while remaining structurally valid +under `@types/react` 16, 17, 18, and 19. + +## Type Parameters + +### T + +`T` diff --git a/docs/reference/type-aliases/SigninCheckResult.md b/docs/reference/type-aliases/SigninCheckResult.md index f6f71bd1..a1ec562b 100644 --- a/docs/reference/type-aliases/SigninCheckResult.md +++ b/docs/reference/type-aliases/SigninCheckResult.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / SigninCheckResult -# Type Alias: SigninCheckResult +# ~~Type Alias: SigninCheckResult~~ > **SigninCheckResult** = \{ `errors`: \{ \}; `hasRequiredClaims`: `false`; `signedIn`: `false`; `user`: `null`; \} \| \{ `errors`: [`ClaimCheckErrors`](../interfaces/ClaimCheckErrors.md); `hasRequiredClaims`: `boolean`; `signedIn`: `true`; `user`: `User`; \} -Defined in: [src/auth.tsx:71](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L71) +Defined in: [src/auth.tsx:291](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L291) + +## Deprecated + +Will be removed in v5.0.0 along with `useSigninCheck`. diff --git a/docs/reference/type-aliases/StorageImageProps.md b/docs/reference/type-aliases/StorageImageProps.md index f4dcf98f..80c2f89d 100644 --- a/docs/reference/type-aliases/StorageImageProps.md +++ b/docs/reference/type-aliases/StorageImageProps.md @@ -4,40 +4,44 @@ [ReactFire reference docs](../README.md) / StorageImageProps -# Type Alias: StorageImageProps +# ~~Type Alias: StorageImageProps~~ > **StorageImageProps** = `object` -Defined in: [src/storage.tsx:40](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L40) +Defined in: [src/storage.tsx:165](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L165) + +## Deprecated + +Use `useStorageDownloadURL(ref)` with a standard `` element instead. Will be removed in v5.0.0. ## Properties -### placeHolder? +### ~~placeHolder?~~ > `optional` **placeHolder?**: `React.ReactNode` -Defined in: [src/storage.tsx:44](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L44) +Defined in: [src/storage.tsx:169](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L169) *** -### storage? +### ~~storage?~~ > `optional` **storage?**: `FirebaseStorage` -Defined in: [src/storage.tsx:42](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L42) +Defined in: [src/storage.tsx:167](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L167) *** -### storagePath +### ~~storagePath~~ > **storagePath**: `string` -Defined in: [src/storage.tsx:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L41) +Defined in: [src/storage.tsx:166](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L166) *** -### suspense? +### ~~suspense?~~ > `optional` **suspense?**: `boolean` -Defined in: [src/storage.tsx:43](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L43) +Defined in: [src/storage.tsx:168](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L168) diff --git a/docs/reference/variables/AnalyticsProvider.md b/docs/reference/variables/AnalyticsProvider.md index 67edf246..4b14f6a0 100644 --- a/docs/reference/variables/AnalyticsProvider.md +++ b/docs/reference/variables/AnalyticsProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / AnalyticsProvider -# Variable: AnalyticsProvider +# ~~Variable: AnalyticsProvider~~ > `const` **AnalyticsProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:74](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L74) +Defined in: [src/sdk.tsx:113](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L113) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:74](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. diff --git a/docs/reference/variables/AnalyticsSdkContext.md b/docs/reference/variables/AnalyticsSdkContext.md index 8d51b3f9..4acdee1b 100644 --- a/docs/reference/variables/AnalyticsSdkContext.md +++ b/docs/reference/variables/AnalyticsSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / AnalyticsSdkContext -# Variable: AnalyticsSdkContext +# ~~Variable: AnalyticsSdkContext~~ > `const` **AnalyticsSdkContext**: `Context`\<`Analytics` \| `undefined`\> -Defined in: [src/sdk.tsx:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L20) +Defined in: [src/sdk.tsx:21](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L21) + +## Deprecated + +Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. diff --git a/docs/reference/variables/AppCheckProvider.md b/docs/reference/variables/AppCheckProvider.md index ff42596a..38b77345 100644 --- a/docs/reference/variables/AppCheckProvider.md +++ b/docs/reference/variables/AppCheckProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / AppCheckProvider -# Variable: AppCheckProvider +# ~~Variable: AppCheckProvider~~ > `const` **AppCheckProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:72](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L72) +Defined in: [src/sdk.tsx:109](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L109) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:72](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. diff --git a/docs/reference/variables/AppCheckSdkContext.md b/docs/reference/variables/AppCheckSdkContext.md index 311519af..cdf759ef 100644 --- a/docs/reference/variables/AppCheckSdkContext.md +++ b/docs/reference/variables/AppCheckSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / AppCheckSdkContext -# Variable: AppCheckSdkContext +# ~~Variable: AppCheckSdkContext~~ > `const` **AppCheckSdkContext**: `Context`\<`AppCheck` \| `undefined`\> -Defined in: [src/sdk.tsx:18](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L18) +Defined in: [src/sdk.tsx:17](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L17) + +## Deprecated + +Pass Firebase SDK instances or references directly instead of using Context Providers. Will be removed in v5.0.0. diff --git a/docs/reference/variables/AuthProvider.md b/docs/reference/variables/AuthProvider.md index 11ea5392..fbf0ed1f 100644 --- a/docs/reference/variables/AuthProvider.md +++ b/docs/reference/variables/AuthProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / AuthProvider -# Variable: AuthProvider +# ~~Variable: AuthProvider~~ > `const` **AuthProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:73](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L73) +Defined in: [src/sdk.tsx:111](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L111) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:73](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass `auth` directly to `useUser(auth)`, `useIdTokenResult(auth)`, or ``. Will be removed in v5.0.0. diff --git a/docs/reference/variables/AuthSdkContext.md b/docs/reference/variables/AuthSdkContext.md index 22b522cb..f4c5f5ad 100644 --- a/docs/reference/variables/AuthSdkContext.md +++ b/docs/reference/variables/AuthSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / AuthSdkContext -# Variable: AuthSdkContext +# ~~Variable: AuthSdkContext~~ > `const` **AuthSdkContext**: `Context`\<`Auth` \| `undefined`\> Defined in: [src/sdk.tsx:19](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L19) + +## Deprecated + +Pass `auth` directly to `useUser(auth)`, `useIdTokenResult(auth)`, or ``. Will be removed in v5.0.0. diff --git a/docs/reference/variables/DatabaseProvider.md b/docs/reference/variables/DatabaseProvider.md index e8069948..124333fc 100644 --- a/docs/reference/variables/DatabaseProvider.md +++ b/docs/reference/variables/DatabaseProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / DatabaseProvider -# Variable: DatabaseProvider +# ~~Variable: DatabaseProvider~~ > `const` **DatabaseProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:75](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L75) +Defined in: [src/sdk.tsx:115](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L115) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:75](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass Realtime Database references directly to `useDatabaseObject` / `useDatabaseList`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/DatabaseSdkContext.md b/docs/reference/variables/DatabaseSdkContext.md index 09acae02..0e401fea 100644 --- a/docs/reference/variables/DatabaseSdkContext.md +++ b/docs/reference/variables/DatabaseSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / DatabaseSdkContext -# Variable: DatabaseSdkContext +# ~~Variable: DatabaseSdkContext~~ > `const` **DatabaseSdkContext**: `Context`\<`Database` \| `undefined`\> -Defined in: [src/sdk.tsx:21](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L21) +Defined in: [src/sdk.tsx:23](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L23) + +## Deprecated + +Pass Realtime Database references directly to `useDatabaseObject` / `useDatabaseList`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/FirestoreProvider.md b/docs/reference/variables/FirestoreProvider.md index d689f31f..837d83b4 100644 --- a/docs/reference/variables/FirestoreProvider.md +++ b/docs/reference/variables/FirestoreProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / FirestoreProvider -# Variable: FirestoreProvider +# ~~Variable: FirestoreProvider~~ > `const` **FirestoreProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:76](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L76) +Defined in: [src/sdk.tsx:117](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L117) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:76](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass Firestore references directly to `useFirestoreDoc` / `useFirestoreCollection`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/FirestoreSdkContext.md b/docs/reference/variables/FirestoreSdkContext.md index 0fcb9ea2..84247c92 100644 --- a/docs/reference/variables/FirestoreSdkContext.md +++ b/docs/reference/variables/FirestoreSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / FirestoreSdkContext -# Variable: FirestoreSdkContext +# ~~Variable: FirestoreSdkContext~~ > `const` **FirestoreSdkContext**: `Context`\<`Firestore` \| `undefined`\> -Defined in: [src/sdk.tsx:22](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L22) +Defined in: [src/sdk.tsx:25](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L25) + +## Deprecated + +Pass Firestore references directly to `useFirestoreDoc` / `useFirestoreCollection`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/FunctionsProvider.md b/docs/reference/variables/FunctionsProvider.md index 918b2609..95930e69 100644 --- a/docs/reference/variables/FunctionsProvider.md +++ b/docs/reference/variables/FunctionsProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / FunctionsProvider -# Variable: FunctionsProvider +# ~~Variable: FunctionsProvider~~ > `const` **FunctionsProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:77](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L77) +Defined in: [src/sdk.tsx:119](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L119) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:77](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. diff --git a/docs/reference/variables/FunctionsSdkContext.md b/docs/reference/variables/FunctionsSdkContext.md index fe59787f..a995bf54 100644 --- a/docs/reference/variables/FunctionsSdkContext.md +++ b/docs/reference/variables/FunctionsSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / FunctionsSdkContext -# Variable: FunctionsSdkContext +# ~~Variable: FunctionsSdkContext~~ > `const` **FunctionsSdkContext**: `Context`\<`Functions` \| `undefined`\> -Defined in: [src/sdk.tsx:23](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L23) +Defined in: [src/sdk.tsx:27](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L27) + +## Deprecated + +Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. diff --git a/docs/reference/variables/PerformanceProvider.md b/docs/reference/variables/PerformanceProvider.md index ee2fcc28..b6bd02fa 100644 --- a/docs/reference/variables/PerformanceProvider.md +++ b/docs/reference/variables/PerformanceProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / PerformanceProvider -# Variable: PerformanceProvider +# ~~Variable: PerformanceProvider~~ > `const` **PerformanceProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:78](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L78) +Defined in: [src/sdk.tsx:121](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L121) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:78](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass `perf` directly to ``. Will be removed in v5.0.0. diff --git a/docs/reference/variables/PerformanceSdkContext.md b/docs/reference/variables/PerformanceSdkContext.md index cff32f29..b1d539fc 100644 --- a/docs/reference/variables/PerformanceSdkContext.md +++ b/docs/reference/variables/PerformanceSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / PerformanceSdkContext -# Variable: PerformanceSdkContext +# ~~Variable: PerformanceSdkContext~~ > `const` **PerformanceSdkContext**: `Context`\<`FirebasePerformance` \| `undefined`\> -Defined in: [src/sdk.tsx:25](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L25) +Defined in: [src/sdk.tsx:31](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L31) + +## Deprecated + +Pass `perf` directly to ``. Will be removed in v5.0.0. diff --git a/docs/reference/variables/RemoteConfigProvider.md b/docs/reference/variables/RemoteConfigProvider.md index 8d3d3625..c259c6e6 100644 --- a/docs/reference/variables/RemoteConfigProvider.md +++ b/docs/reference/variables/RemoteConfigProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / RemoteConfigProvider -# Variable: RemoteConfigProvider +# ~~Variable: RemoteConfigProvider~~ > `const` **RemoteConfigProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:80](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L80) +Defined in: [src/sdk.tsx:125](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L125) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:80](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. diff --git a/docs/reference/variables/RemoteConfigSdkContext.md b/docs/reference/variables/RemoteConfigSdkContext.md index 00fbb0f7..942bc35a 100644 --- a/docs/reference/variables/RemoteConfigSdkContext.md +++ b/docs/reference/variables/RemoteConfigSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / RemoteConfigSdkContext -# Variable: RemoteConfigSdkContext +# ~~Variable: RemoteConfigSdkContext~~ > `const` **RemoteConfigSdkContext**: `Context`\<`RemoteConfig` \| `undefined`\> -Defined in: [src/sdk.tsx:26](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L26) +Defined in: [src/sdk.tsx:33](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L33) + +## Deprecated + +Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. diff --git a/docs/reference/variables/StorageProvider.md b/docs/reference/variables/StorageProvider.md index dbcb81c1..91d964cd 100644 --- a/docs/reference/variables/StorageProvider.md +++ b/docs/reference/variables/StorageProvider.md @@ -4,11 +4,11 @@ [ReactFire reference docs](../README.md) / StorageProvider -# Variable: StorageProvider +# ~~Variable: StorageProvider~~ > `const` **StorageProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:79](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L79) +Defined in: [src/sdk.tsx:123](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L123) ## Parameters @@ -19,3 +19,7 @@ Defined in: [src/sdk.tsx:79](https://github.com/FirebaseExtended/reactfire/blob/ ## Returns `ReactElement` + +## Deprecated + +Pass Storage references directly to `useStorageDownloadURL`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/StorageSdkContext.md b/docs/reference/variables/StorageSdkContext.md index e921e113..64b02e27 100644 --- a/docs/reference/variables/StorageSdkContext.md +++ b/docs/reference/variables/StorageSdkContext.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / StorageSdkContext -# Variable: StorageSdkContext +# ~~Variable: StorageSdkContext~~ > `const` **StorageSdkContext**: `Context`\<`FirebaseStorage` \| `undefined`\> -Defined in: [src/sdk.tsx:24](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L24) +Defined in: [src/sdk.tsx:29](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L29) + +## Deprecated + +Pass Storage references directly to `useStorageDownloadURL`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitAnalytics.md b/docs/reference/variables/useInitAnalytics.md index 80cddfb2..1bf7317d 100644 --- a/docs/reference/variables/useInitAnalytics.md +++ b/docs/reference/variables/useInitAnalytics.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitAnalytics -# Variable: useInitAnalytics +# ~~Variable: useInitAnalytics~~ > `const` **useInitAnalytics**: `InitSdkHook`\<`Analytics`\> -Defined in: [src/sdk.tsx:99](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L99) +Defined in: [src/sdk.tsx:156](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L156) + +## Deprecated + +Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitAppCheck.md b/docs/reference/variables/useInitAppCheck.md index 810fc3f0..5d9eb0a4 100644 --- a/docs/reference/variables/useInitAppCheck.md +++ b/docs/reference/variables/useInitAppCheck.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitAppCheck -# Variable: useInitAppCheck +# ~~Variable: useInitAppCheck~~ > `const` **useInitAppCheck**: `InitSdkHook`\<`AppCheck`\> -Defined in: [src/sdk.tsx:97](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L97) +Defined in: [src/sdk.tsx:152](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L152) + +## Deprecated + +Initialize Firebase SDKs at module scope (`initializeFirestore`, etc.) instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitAuth.md b/docs/reference/variables/useInitAuth.md index bd29275b..948f6123 100644 --- a/docs/reference/variables/useInitAuth.md +++ b/docs/reference/variables/useInitAuth.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitAuth -# Variable: useInitAuth +# ~~Variable: useInitAuth~~ > `const` **useInitAuth**: `InitSdkHook`\<`Auth`\> -Defined in: [src/sdk.tsx:98](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L98) +Defined in: [src/sdk.tsx:154](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L154) + +## Deprecated + +Initialize Firebase SDKs at module scope (`initializeAuth`, etc.) instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitDatabase.md b/docs/reference/variables/useInitDatabase.md index 4bb0de18..a4944054 100644 --- a/docs/reference/variables/useInitDatabase.md +++ b/docs/reference/variables/useInitDatabase.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitDatabase -# Variable: useInitDatabase +# ~~Variable: useInitDatabase~~ > `const` **useInitDatabase**: `InitSdkHook`\<`Database`\> -Defined in: [src/sdk.tsx:100](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L100) +Defined in: [src/sdk.tsx:158](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L158) + +## Deprecated + +Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitFirestore.md b/docs/reference/variables/useInitFirestore.md index 5ef24c7e..8dfe35fd 100644 --- a/docs/reference/variables/useInitFirestore.md +++ b/docs/reference/variables/useInitFirestore.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitFirestore -# Variable: useInitFirestore +# ~~Variable: useInitFirestore~~ > `const` **useInitFirestore**: `InitSdkHook`\<`Firestore`\> -Defined in: [src/sdk.tsx:101](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L101) +Defined in: [src/sdk.tsx:160](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L160) + +## Deprecated + +Initialize Firebase SDKs at module scope (`initializeFirestore`) instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitFunctions.md b/docs/reference/variables/useInitFunctions.md index 63c7eed4..bc3cfee1 100644 --- a/docs/reference/variables/useInitFunctions.md +++ b/docs/reference/variables/useInitFunctions.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitFunctions -# Variable: useInitFunctions +# ~~Variable: useInitFunctions~~ > `const` **useInitFunctions**: `InitSdkHook`\<`Functions`\> -Defined in: [src/sdk.tsx:102](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L102) +Defined in: [src/sdk.tsx:162](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L162) + +## Deprecated + +Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitPerformance.md b/docs/reference/variables/useInitPerformance.md index 150cdfdd..ac42980c 100644 --- a/docs/reference/variables/useInitPerformance.md +++ b/docs/reference/variables/useInitPerformance.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitPerformance -# Variable: useInitPerformance +# ~~Variable: useInitPerformance~~ > `const` **useInitPerformance**: `InitSdkHook`\<`FirebasePerformance`\> -Defined in: [src/sdk.tsx:103](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L103) +Defined in: [src/sdk.tsx:164](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L164) + +## Deprecated + +Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitRemoteConfig.md b/docs/reference/variables/useInitRemoteConfig.md index f9871886..238c54f5 100644 --- a/docs/reference/variables/useInitRemoteConfig.md +++ b/docs/reference/variables/useInitRemoteConfig.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitRemoteConfig -# Variable: useInitRemoteConfig +# ~~Variable: useInitRemoteConfig~~ > `const` **useInitRemoteConfig**: `InitSdkHook`\<`RemoteConfig`\> -Defined in: [src/sdk.tsx:105](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L105) +Defined in: [src/sdk.tsx:167](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L167) + +## Deprecated + +Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/useInitStorage.md b/docs/reference/variables/useInitStorage.md index a38823da..1a80c0b5 100644 --- a/docs/reference/variables/useInitStorage.md +++ b/docs/reference/variables/useInitStorage.md @@ -4,8 +4,12 @@ [ReactFire reference docs](../README.md) / useInitStorage -# Variable: useInitStorage +# ~~Variable: useInitStorage~~ > `const` **useInitStorage**: `InitSdkHook`\<`FirebaseStorage`\> -Defined in: [src/sdk.tsx:107](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L107) +Defined in: [src/sdk.tsx:170](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L170) + +## Deprecated + +Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. diff --git a/docs/reference/variables/version.md b/docs/reference/variables/version.md index 35c34d90..23265881 100644 --- a/docs/reference/variables/version.md +++ b/docs/reference/variables/version.md @@ -8,4 +8,4 @@ > `const` **version**: `string` -Defined in: [src/firebaseApp.tsx:22](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L22) +Defined in: [src/firebaseApp.tsx:28](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L28) diff --git a/package-lock.json b/package-lock.json index 1e974b3b..a0cf45e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "4.2.6", "license": "MIT", "dependencies": { - "rxfire": "^6.1.0", "rxjs": "^6.6.3 || ^7.0.1", "use-sync-external-store": "^1.2.0" }, @@ -313,6 +312,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-1.4.1.tgz", "integrity": "sha512-bcusQfA/tHjUjBTnMx6jdoPMpDl3r8K15Z+snHz9wq0Foox0F/V+kNLXucEOHoTL2hTc9l+onZCyBJs2QoIC3g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", @@ -333,6 +333,7 @@ "version": "0.10.17", "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.17.tgz", "integrity": "sha512-n5vfBbvzduMou/2cqsnKrIes4auaBjdhg8QNA2ZQZ59QgtO2QiwBaXQZQE4O4sgB0Ds1tvLgUUkY+pwzu6/xEg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -349,6 +350,7 @@ "version": "0.2.23", "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.23.tgz", "integrity": "sha512-3AdO10RN18G5AzREPoFgYhW6vWXr3u+OYQv6pl3CX6Fky8QRk0AHurZlY3Q1xkXO0TDxIsdhO3y65HF7PBOJDw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/analytics": "0.10.17", @@ -365,12 +367,14 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/app": { "version": "0.13.2", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz", "integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -387,6 +391,7 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.10.1.tgz", "integrity": "sha512-MgNdlms9Qb0oSny87pwpjKush9qUwCJhfmTJHDfrcKo4neLGiSeVE4qJkzP7EQTIUFKp84pbTxobSAXkiuQVYQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -405,6 +410,7 @@ "version": "0.3.26", "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.26.tgz", "integrity": "sha512-PkX+XJMLDea6nmnopzFKlr+s2LMQGqdyT2DHdbx1v1dPSqOol2YzgpgymmhC67vitXVpNvS3m/AiWQWWhhRRPQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-check": "0.10.1", @@ -425,18 +431,21 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/app-check-types": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/app-compat": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz", "integrity": "sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app": "0.13.2", @@ -453,12 +462,14 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/auth-compat": { "version": "0.5.28", "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.28.tgz", "integrity": "sha512-HpMSo/cc6Y8IX7bkRIaPPqT//Jt83iWy5rmDWeThXQCAImstkdNo3giFLORJwrZw2ptiGkOij64EH1ztNJzc7Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/auth": "1.10.8", @@ -478,6 +489,7 @@ "version": "1.10.8", "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz", "integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -502,12 +514,14 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/auth-types": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -518,6 +532,7 @@ "version": "0.6.18", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.18.tgz", "integrity": "sha512-n28kPCkE2dL2U28fSxZJjzPPVpKsQminJ6NrzcKXAI0E/lYC8YhfwpyllScqVEvAI3J2QgJZWYgrX+1qGI+SQQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/util": "1.12.1", @@ -531,6 +546,7 @@ "version": "0.3.10", "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.10.tgz", "integrity": "sha512-VMVk7zxIkgwlVQIWHOKFahmleIjiVFwFOjmakXPd/LDgaB/5vzwsB5DWIYo+3KhGxWpidQlR8geCIn39YflJIQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/auth-interop-types": "0.2.4", @@ -547,6 +563,7 @@ "version": "1.0.20", "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.20.tgz", "integrity": "sha512-H9Rpj1pQ1yc9+4HQOotFGLxqAXwOzCHsRSRjcQFNOr8lhUt6LeYjf0NSRL04sc4X0dWe8DsCvYKxMYvFG/iOJw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", @@ -565,6 +582,7 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.11.tgz", "integrity": "sha512-itEsHARSsYS95+udF/TtIzNeQ0Uhx4uIna0sk4E0wQJBUnLc/G1X6D7oRljoOuwwCezRLGvWBRyNrugv/esOEw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -582,6 +600,7 @@ "version": "1.0.15", "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.15.tgz", "integrity": "sha512-XWHJ0VUJ0k2E9HDMlKxlgy/ZuTa9EvHCGLjaKSUvrQnwhgZuRU5N3yX6SZ+ftf2hTzZmfRkv+b3QRvGg40bKNw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-types": "0.9.3", @@ -592,6 +611,7 @@ "version": "4.8.0", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.8.0.tgz", "integrity": "sha512-QSRk+Q1/CaabKyqn3C32KSFiOdZpSqI9rpLK5BHPcooElumOBooPFa6YkDdiT+/KhJtel36LdAacha9BptMj2A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -613,6 +633,7 @@ "version": "0.3.53", "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.53.tgz", "integrity": "sha512-qI3yZL8ljwAYWrTousWYbemay2YZa+udLWugjdjju2KODWtLG94DfO4NALJgPLv8CVGcDHNFXoyQexdRA0Cz8Q==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -632,6 +653,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -642,6 +664,7 @@ "version": "0.12.9", "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.9.tgz", "integrity": "sha512-FG95w6vjbUXN84Ehezc2SDjGmGq225UYbHrb/ptkRT7OTuCiQRErOQuyt1jI1tvcDekdNog+anIObihNFz79Lg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", @@ -662,6 +685,7 @@ "version": "0.3.26", "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.26.tgz", "integrity": "sha512-A798/6ff5LcG2LTWqaGazbFYnjBW8zc65YfID/en83ALmkhu2b0G8ykvQnLtakbV9ajrMYPn7Yc/XcYsZIUsjA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -681,12 +705,14 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/installations": { "version": "0.6.18", "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.18.tgz", "integrity": "sha512-NQ86uGAcvO8nBRwVltRL9QQ4Reidc/3whdAasgeWCPIcrhOKDuNpAALa6eCVryLnK14ua2DqekCOX5uC9XbU/A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -702,6 +728,7 @@ "version": "0.2.18", "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.18.tgz", "integrity": "sha512-aLFohRpJO5kKBL/XYL4tN+GdwEB/Q6Vo9eZOM/6Kic7asSUgmSfGPpGUZO1OAaSRGwF4Lqnvi1f/f9VZnKzChw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -718,6 +745,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x" @@ -727,6 +755,7 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -739,6 +768,7 @@ "version": "0.12.22", "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.22.tgz", "integrity": "sha512-GJcrPLc+Hu7nk+XQ70Okt3M1u1eRr2ZvpMbzbc54oTPJZySHcX9ccZGVFcsZbSZ6o1uqumm8Oc7OFkD3Rn1/og==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -756,6 +786,7 @@ "version": "0.2.22", "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.22.tgz", "integrity": "sha512-5ZHtRnj6YO6f/QPa/KU6gryjmX4Kg33Kn4gRpNU6M1K47Gm8kcQwPkX7erRUYEH1mIWptfvjvXMHWoZaWjkU7A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -771,12 +802,14 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/performance": { "version": "0.7.7", "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.7.tgz", "integrity": "sha512-JTlTQNZKAd4+Q5sodpw6CN+6NmwbY72av3Lb6wUKTsL7rb3cuBIhQSrslWbVz0SwK3x0ZNcqX24qtRbwKiv+6w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -794,6 +827,7 @@ "version": "0.2.20", "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.20.tgz", "integrity": "sha512-XkFK5NmOKCBuqOKWeRgBUFZZGz9SzdTZp4OqeUg+5nyjapTiZ4XoiiUL8z7mB2q+63rPmBl7msv682J3rcDXIQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -811,12 +845,14 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/remote-config": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.5.tgz", "integrity": "sha512-fU0c8HY0vrVHwC+zQ/fpXSqHyDMuuuglV94VF6Yonhz8Fg2J+KOowPGANM0SZkLvVOYpTeWp3ZmM+F6NjwWLnw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -833,6 +869,7 @@ "version": "0.2.18", "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.18.tgz", "integrity": "sha512-YiETpldhDy7zUrnS8e+3l7cNs0sL7+tVAxvVYU0lu7O+qLHbmdtAxmgY+wJqWdW2c9nDvBFec7QiF58pEUu0qQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -850,12 +887,14 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz", "integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@firebase/storage": { "version": "0.13.14", "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.14.tgz", "integrity": "sha512-xTq5ixxORzx+bfqCpsh+o3fxOsGoDjC1nO0Mq2+KsOcny3l7beyBhP/y1u5T6mgsFQwI1j6oAkbT5cWdDBx87g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -873,6 +912,7 @@ "version": "0.3.24", "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.24.tgz", "integrity": "sha512-XHn2tLniiP7BFKJaPZ0P8YQXKiVJX+bMyE2j2YWjYfaddqiJnROJYqSomwW6L3Y+gZAga35ONXUJQju6MB6SOQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -892,6 +932,7 @@ "version": "0.8.3", "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "dev": true, "license": "Apache-2.0", "peerDependencies": { "@firebase/app-types": "0.x", @@ -902,6 +943,7 @@ "version": "1.12.1", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.1.tgz", "integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==", + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -915,6 +957,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz", "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==", + "dev": true, "license": "Apache-2.0" }, "node_modules/@gerrit0/mini-shiki": { @@ -1258,6 +1301,7 @@ "version": "1.9.16", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.16.tgz", "integrity": "sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.8", @@ -1271,6 +1315,7 @@ "version": "0.7.15", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", @@ -1289,6 +1334,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -1303,6 +1349,7 @@ "version": "17.7.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -1321,6 +1368,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -2566,29 +2614,34 @@ "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true }, "node_modules/@protobufjs/codegen": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1" @@ -2597,22 +2650,26 @@ "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true }, "node_modules/@protobufjs/utf8": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@rolldown/binding-android-arm-eabi": { @@ -3736,6 +3793,7 @@ "version": "25.9.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": ">=7.24.0 <7.24.7" @@ -4385,6 +4443,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -4393,6 +4452,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -5502,6 +5562,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5512,7 +5573,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/color-string": { "version": "1.9.1", @@ -6439,7 +6501,8 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/emojilib": { "version": "2.4.0", @@ -6663,6 +6726,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, "engines": { "node": ">=6" } @@ -7476,6 +7540,7 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" @@ -7612,6 +7677,7 @@ "version": "11.10.0", "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.10.0.tgz", "integrity": "sha512-nKBXoDzF0DrXTBQJlZa+sbC5By99ysYU1D6PkMRYknm0nCW7rJly47q492Ht7Ndz5MeYSBuboKuhS1e6mFC03w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/ai": "1.4.1", @@ -7843,6 +7909,7 @@ "version": "1.10.8", "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz", "integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@firebase/component": "0.6.18", @@ -8139,6 +8206,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -9117,6 +9185,7 @@ "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, "license": "MIT" }, "node_modules/http-proxy-agent": { @@ -9177,6 +9246,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true, "license": "ISC" }, "node_modules/ieee754": { @@ -9519,6 +9589,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -10990,7 +11061,8 @@ "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -11135,6 +11207,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, "license": "Apache-2.0" }, "node_modules/loose-envify": { @@ -12832,6 +12905,7 @@ "version": "7.6.6", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -13368,6 +13442,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -13688,16 +13763,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxfire": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/rxfire/-/rxfire-6.1.0.tgz", - "integrity": "sha512-NezdjeY32VZcCuGO0bbb8H8seBsJSCaWdUwGsHNzUcAOHR0VGpzgPtzjuuLXr8R/iemkqSzbx/ioS7VwV43ynA==", - "license": "Apache-2.0", - "peerDependencies": { - "firebase": "^9.0.0 || ^10.0.0 || ^11.0.0", - "rxjs": "^6.0.0 || ^7.0.0" - } - }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -13710,6 +13775,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -14361,6 +14427,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14454,6 +14521,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -15376,6 +15444,7 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { @@ -15796,6 +15865,7 @@ "version": "4.2.4", "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/webidl-conversions": { @@ -15811,6 +15881,7 @@ "version": "0.7.5", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", @@ -15825,6 +15896,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=0.8.0" @@ -16028,6 +16100,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16145,6 +16218,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "engines": { "node": ">=10" } diff --git a/package.json b/package.json index 6b6b707f..ec063c1d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "version": "4.2.6", + "sideEffects": false, "license": "MIT", "type": "module", "main": "dist/index.umd.cjs", @@ -107,7 +108,6 @@ "vitest": "^4.1.9" }, "dependencies": { - "rxfire": "^6.1.0", "rxjs": "^6.6.3 || ^7.0.1", "use-sync-external-store": "^1.2.0" }, diff --git a/src/SuspenseSubject.ts b/src/SuspenseSubject.ts index 5b7ef884..18146dc0 100644 --- a/src/SuspenseSubject.ts +++ b/src/SuspenseSubject.ts @@ -1,12 +1,16 @@ import { empty, Observable, Subject, Subscriber, Subscription } from 'rxjs'; import { catchError, shareReplay, tap } from 'rxjs/operators'; -import { ObservableStatus } from './useObservable.js'; +import { attachUsable, createFulfilledUsable, instrumentPromise } from './useFirebaseStream.js'; +import type { ObservableStatus } from './useFirebaseStream.js'; +/** + * @deprecated RxJS integration (`SuspenseSubject`, `useObservable`, `preloadObservable`) is deprecated and will be removed in v5.0.0. + */ export class SuspenseSubject extends Subject { private _value: T | undefined; private _hasValue = false; private _timeoutHandler: ReturnType; - private _firstEmission: Promise; + private _firstEmission: Promise & { status?: 'pending' | 'fulfilled' | 'rejected'; value?: void; reason?: any }; private _error: any = undefined; private _innerObservable: Observable; private _warmupSubscription: Subscription; @@ -21,15 +25,19 @@ export class SuspenseSubject extends Subject { constructor(innerObservable: Observable, private _timeoutWindow: number, private _suspenseEnabled: boolean) { super(); this._firstEmission = new Promise((resolve) => (this._resolveFirstEmission = resolve)); + this._firstEmission.status = 'pending'; - this._immutableStatus = { - status: 'loading', - hasEmitted: false, - isComplete: false, - data: undefined, - error: undefined, - firstValuePromise: this._firstEmission - } as ObservableStatus; + this._immutableStatus = attachUsable( + { + status: 'loading', + hasEmitted: false, + isComplete: false, + data: undefined as unknown as T, + error: undefined, + firstValuePromise: this._firstEmission, + }, + () => instrumentPromise(this._firstEmission.then(() => this.value)) + ); this._innerObservable = innerObservable.pipe( tap({ @@ -40,13 +48,15 @@ export class SuspenseSubject extends Subject { // save the error, so that we can raise on subscription or .value // resolve the promise, so suspense tries again this._error = e; + this._firstEmission.status = 'fulfilled'; + this._firstEmission.value = undefined; this._resolveFirstEmission(); this._updateImmutableStatus(); }, complete: () => { this._isComplete = true; this._updateImmutableStatus(); - } + }, }), catchError(() => empty()), shareReplay(1) @@ -91,19 +101,27 @@ export class SuspenseSubject extends Subject { // Cast required because `data` is internally `T | undefined` while a value is // still in flight, whereas ObservableStatus exposes `data: T` to consumers. // The relationships between the status fields are verified in tests instead. - this._immutableStatus = { - status: this._error ? 'error' : (this._hasValue ? 'success' : 'loading'), - hasEmitted: this._hasValue, - isComplete: this._isComplete, - data: this._value, - error: this._error, - firstValuePromise: this._firstEmission - } as ObservableStatus; + this._immutableStatus = attachUsable( + { + status: this._error ? 'error' : this._hasValue ? 'success' : 'loading', + hasEmitted: this._hasValue, + isComplete: this._isComplete, + data: this._value as T, + error: this._error, + firstValuePromise: this._firstEmission, + }, + () => + this._hasValue + ? createFulfilledUsable(this._value as T) + : instrumentPromise(this._firstEmission.then(() => this.value)) + ); } private _next(value: T) { this._hasValue = true; this._value = value; + this._firstEmission.status = 'fulfilled'; + this._firstEmission.value = undefined; this._resolveFirstEmission(); this._updateImmutableStatus(); } @@ -117,6 +135,7 @@ export class SuspenseSubject extends Subject { this._value = undefined; this._error = undefined; this._firstEmission = new Promise((resolve) => (this._resolveFirstEmission = resolve)); + this._firstEmission.status = 'pending'; this._updateImmutableStatus(); } diff --git a/src/auth.tsx b/src/auth.tsx index 6e74d6c5..7e2cce78 100644 --- a/src/auth.tsx +++ b/src/auth.tsx @@ -1,62 +1,276 @@ import * as React from 'react'; -import { user } from 'rxfire/auth'; -import { preloadObservable, ReactFireOptions, useAuth, useObservable, ObservableStatus, ReactFireError } from './index.js'; -import { from, of, defer } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; +import { onIdTokenChanged } from 'firebase/auth'; +import { AuthSdkContext } from './sdk.js'; import { useSuspenseEnabledFromConfigAndContext } from './firebaseApp.js'; +import { + InstrumentedPromise, + ObservableStatus, + ReactFireError, + ReactFireOptions, + ReactFireUsable, + attachUsable, + createFulfilledUsable, + getInitialDataOption, + hasInitialDataOption, + instrumentPromise, + unwrapOrSuspend, + useFirebaseStream, +} from './useFirebaseStream.js'; import type { Auth, User, IdTokenResult } from 'firebase/auth'; type Claims = IdTokenResult['claims']; +const authReadyWeakMap = new WeakMap>(); + +function getAuthReady(auth: Auth): InstrumentedPromise { + let promise = authReadyWeakMap.get(auth); + if (!promise) { + if ((auth as { _isInitialized?: boolean })._isInitialized || auth.currentUser) { + promise = createFulfilledUsable(auth.currentUser); + } else { + promise = instrumentPromise(auth.authStateReady().then(() => auth.currentUser)); + } + authReadyWeakMap.set(auth, promise); + } else if (promise.status === 'fulfilled' && promise.value !== auth.currentUser) { + promise = createFulfilledUsable(auth.currentUser); + authReadyWeakMap.set(auth, promise); + } + return promise; +} + +const IN_FLIGHT_EVICT_MS = 30_000; +const inFlightTokenPromises = new WeakMap>>(); +const inFlightAuthTokenPromises = new WeakMap>>(); + +function getOrStartTokenPromise(user: User, forceRefresh: boolean): InstrumentedPromise { + let byRefresh = inFlightTokenPromises.get(user); + if (!byRefresh) { + byRefresh = new Map(); + inFlightTokenPromises.set(user, byRefresh); + } + let promise = byRefresh.get(forceRefresh); + if (!promise) { + promise = instrumentPromise(user.getIdTokenResult(forceRefresh)); + byRefresh.set(forceRefresh, promise); + const cleanup = () => + setTimeout(() => { + if (byRefresh?.get(forceRefresh) === promise) { + byRefresh.delete(forceRefresh); + } + }, IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); + } + return promise; +} + +function getOrStartAuthTokenPromise(auth: Auth, forceRefresh: boolean): InstrumentedPromise { + let byRefresh = inFlightAuthTokenPromises.get(auth); + if (!byRefresh) { + byRefresh = new Map(); + inFlightAuthTokenPromises.set(auth, byRefresh); + } + let promise = byRefresh.get(forceRefresh); + if (!promise) { + promise = instrumentPromise( + getAuthReady(auth).then((user) => (user ? user.getIdTokenResult(forceRefresh) : null)) + ); + byRefresh.set(forceRefresh, promise); + const cleanup = () => + setTimeout(() => { + if (byRefresh?.get(forceRefresh) === promise) { + byRefresh.delete(forceRefresh); + } + }, IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); + } + return promise; +} + +function isAuthInstance(val: unknown): val is Auth { + return Boolean(val && typeof val === 'object' && typeof (val as Auth).authStateReady === 'function'); +} + +/** + * @deprecated Unwrap `useUser(auth).experimental_usable` with `React.use()` instead. Will be removed in v5.0.0. + */ export async function preloadUser(authResolver: () => Promise) { const auth = await authResolver(); - const user$ = preloadObservable(user(auth), `auth:user:${auth.name}`); - return user$.toPromise(); + await getAuthReady(auth); + return auth.currentUser; } /** * Subscribe to Firebase auth state changes, including token refresh * - * @param options + * @param authOrOptions - Optional `Auth` instance (recommended for v5 compatibility) or `ReactFireOptions` + * @param maybeOptions - `ReactFireOptions` when `auth` is provided as the first argument */ -export function useUser(options?: ReactFireOptions): ObservableStatus { - const auth = useAuth(); - - const observableId = `auth:user:${auth.name}`; - const observable$ = user(auth); - - const _options: ReactFireOptions = { ...options }; - - // If a user is already signed in, seed initialData so consumers see the user - // synchronously on the first render without waiting for the async observable. - // We only do this when currentUser is truthy to avoid masking the uninitialized - // (null before auth has loaded from storage) case as "signed out". - if (auth.currentUser && !('initialData' in _options) && !('startWithValue' in _options)) { - _options.initialData = auth.currentUser as unknown as T; +export function useUser(options?: ReactFireOptions): ObservableStatus; +export function useUser(auth: Auth, options?: ReactFireOptions): ObservableStatus; +export function useUser( + authOrOptions?: Auth | ReactFireOptions, + maybeOptions?: ReactFireOptions +): ObservableStatus { + const contextAuth = React.useContext(AuthSdkContext); + const hasExplicitAuth = isAuthInstance(authOrOptions); + const auth = hasExplicitAuth ? authOrOptions : contextAuth; + if (!auth) { + throw new Error('SDK not found. useSdk must be called from within a provider'); } + const options = hasExplicitAuth ? maybeOptions : (authOrOptions as ReactFireOptions | undefined); + const authReady = getAuthReady(auth); + + return useFirebaseStream({ + ref: auth, + peekInFlight: () => authReadyWeakMap.get(auth), + getOrStartInFlight: () => getAuthReady(auth), + clearInFlight: () => {}, + getSyncInitialValue: () => { + const isReady = Boolean(auth.currentUser) || authReady.status === 'fulfilled'; + return isReady + ? { hasValue: true, value: auth.currentUser, isComplete: false } + : { hasValue: false, value: null }; + }, + subscribe: (onNext, onError) => { + let active = true; + const readyPromise = getAuthReady(auth); + if (auth.currentUser || readyPromise.status === 'fulfilled') { + onNext(auth.currentUser, false); + } else { + readyPromise.then( + () => { + if (active) { + onNext(auth.currentUser, false); + } + }, + () => {} + ); + } - return useObservable(observableId, observable$, _options); + const unsubscribe = onIdTokenChanged( + auth, + (nextUser) => { + if (active) { + onNext(nextUser, false); + } + }, + (err) => { + if (active) { + onError(err); + } + } + ); + + return () => { + active = false; + unsubscribe(); + }; + }, + options, + }); } -export function useIdTokenResult(user: User, forceRefresh = false, options?: ReactFireOptions): ObservableStatus { - if (!user) { +export function useIdTokenResult( + user: User, + forceRefresh?: boolean, + options?: ReactFireOptions +): ObservableStatus; +export function useIdTokenResult( + auth: Auth, + forceRefresh?: boolean, + options?: ReactFireOptions +): ObservableStatus; +export function useIdTokenResult( + userOrAuth: User | Auth, + forceRefresh = false, + options?: ReactFireOptions +): ObservableStatus { + if (!userOrAuth) { throw new Error('you must provide a user'); } - const observableId = `auth:idTokenResult:${user.uid}:forceRefresh=${forceRefresh}`; - // Wrap in `defer` so the token fetch runs lazily on subscription rather than - // eagerly on every render. - const observable$ = defer(() => from(user.getIdTokenResult(forceRefresh))); + const isAuth = isAuthInstance(userOrAuth); + + return useFirebaseStream<{ target: User | Auth; forceRefresh: boolean }, any>({ + ref: { target: userOrAuth, forceRefresh }, + isEqual: (a, b) => a.target === b.target && a.forceRefresh === b.forceRefresh, + peekInFlight: () => + isAuth + ? inFlightAuthTokenPromises.get(userOrAuth)?.get(forceRefresh) + : inFlightTokenPromises.get(userOrAuth)?.get(forceRefresh), + getOrStartInFlight: () => + isAuth + ? getOrStartAuthTokenPromise(userOrAuth, forceRefresh) + : getOrStartTokenPromise(userOrAuth, forceRefresh), + clearInFlight: (expectedPromise) => { + if (isAuth) { + const byRefresh = inFlightAuthTokenPromises.get(userOrAuth); + if (byRefresh && (!expectedPromise || byRefresh.get(forceRefresh) === expectedPromise)) { + byRefresh.delete(forceRefresh); + } + } else { + const byRefresh = inFlightTokenPromises.get(userOrAuth); + if (byRefresh && (!expectedPromise || byRefresh.get(forceRefresh) === expectedPromise)) { + byRefresh.delete(forceRefresh); + } + } + }, + subscribe: (onNext, onError, inFlightPromise) => { + if (isAuth) { + let active = true; + let lastUser: User | null = null; + const unsubscribe = onIdTokenChanged( + userOrAuth, + (nextUser) => { + if (!active) return; + if (!nextUser) { + lastUser = null; + onNext(null, false); + return; + } + const shouldForce = nextUser !== lastUser && forceRefresh; + lastUser = nextUser; + getOrStartTokenPromise(nextUser, shouldForce).then( + (res) => { + if (active) onNext(res, false); + }, + (err) => { + if (active) onError(err); + } + ); + }, + (err) => { + if (active) onError(err); + } + ); + return () => { + active = false; + unsubscribe(); + }; + } - return useObservable(observableId, observable$, options); + const tokenPromise = (inFlightPromise as InstrumentedPromise | undefined) ?? getOrStartTokenPromise(userOrAuth, forceRefresh); + tokenPromise.then( + (result) => onNext(result, true), + (error: Error) => onError(error) + ); + return () => {}; + }, + options, + defaultIsComplete: !isAuth, + }); } export interface AuthCheckProps { + auth?: Auth; fallback: React.ReactNode; children: React.ReactNode; requiredClaims?: Object; } +/** + * @deprecated Use `` or `useIdTokenResult(auth)` instead. Will be removed in v5.0.0. + */ export interface ClaimsCheckProps { user: User; fallback: React.ReactNode; @@ -64,10 +278,16 @@ export interface ClaimsCheckProps { requiredClaims: { [key: string]: any }; } +/** + * @deprecated Will be removed in v5.0.0 along with `useSigninCheck`. + */ export interface ClaimCheckErrors { [key: string]: any[]; } +/** + * @deprecated Will be removed in v5.0.0 along with `useSigninCheck`. + */ export type SigninCheckResult = | { signedIn: false; @@ -82,14 +302,23 @@ export type SigninCheckResult = user: User; }; +/** + * @deprecated Will be removed in v5.0.0 along with `useSigninCheck`. + */ export interface SignInCheckOptionsBasic extends ReactFireOptions { forceRefresh?: boolean; } +/** + * @deprecated Will be removed in v5.0.0 along with `useSigninCheck`. + */ export interface SignInCheckOptionsClaimsObject extends SignInCheckOptionsBasic { requiredClaims: Claims; } +/** + * @deprecated Will be removed in v5.0.0 along with `useSigninCheck`. + */ export interface ClaimsValidator { (claims: Claims): { hasRequiredClaims: boolean; @@ -97,95 +326,247 @@ export interface ClaimsValidator { }; } +/** + * @deprecated Will be removed in v5.0.0 along with `useSigninCheck`. + */ export interface SignInCheckOptionsClaimsValidator extends SignInCheckOptionsBasic { validateCustomClaims: ClaimsValidator; } +const signinCheckUsableCache = new WeakMap>>(); + /** * Subscribe to the signed-in status of a user. * - * ```ts - * const { status, data:signInCheckResult } = useSigninCheck(); - * - * if (status === 'loading') { - * return } - * - * - * if (signInCheckResult.signedIn === true) { - * return - * } else { - * return - * } - * ``` - * - * Optionally check [custom claims](https://firebase.google.com/docs/auth/admin/custom-claims) of a user as well. - * - * ```ts - * // pass in an object describing the custom claims a user must have - * const {status, data: signInCheckResult} = useSigninCheck({requiredClaims: {admin: true}}); - * - * // pass in a custom claims validator function - * const {status, data: signInCheckResult} = useSigninCheck({validateCustomClaims: (userClaims) => { - * // custom validation logic... - * }}); - * - * // You can optionally force-refresh the token - * const {status, data: signInCheckResult} = useSigninCheck({forceRefresh: true, requiredClaims: {admin: true}}); - * ``` + * @deprecated Use `useUser(auth)`, `useIdTokenResult(auth)`, or `` instead. Will be removed in v5.0.0. */ export function useSigninCheck( options?: SignInCheckOptionsBasic | SignInCheckOptionsClaimsObject | SignInCheckOptionsClaimsValidator ): ObservableStatus { - // If both `requiredClaims` and `validateCustomClaims` are provided, we won't know which one to use if (options?.hasOwnProperty('requiredClaims') && options?.hasOwnProperty('validateCustomClaims')) { throw new Error('Cannot have both "requiredClaims" and "validateCustomClaims". Use one or the other.'); } - const auth = useAuth(); + const suspenseEnabled = useSuspenseEnabledFromConfigAndContext(options?.suspense); + const hasInitialData = hasInitialDataOption(options); + const initialData = getInitialDataOption(options); - // ObservableId should change for different options configurations to ensure no cache collisions - let observableId = `auth:signInCheck:${auth.name}::forceRefresh:${!!options?.forceRefresh}`; - if (options?.forceRefresh) { - observableId = `${observableId}:forceRefresh:${options.forceRefresh}`; - } - if (options?.hasOwnProperty('requiredClaims')) { - observableId = `${observableId}:requiredClaims:${JSON.stringify((options as SignInCheckOptionsClaimsObject).requiredClaims)}`; - } else if (options?.hasOwnProperty('validateCustomClaims')) { - // TODO(jamesdaniels): Check if stringifying this function breaks in IE11 - observableId = `${observableId}:validateCustomClaims:${JSON.stringify((options as SignInCheckOptionsClaimsValidator).validateCustomClaims)}`; - } + const userStatusObj = useUser({ + suspense: hasInitialData ? false : options?.suspense, + }); + const { status: userStatus, data: currentUser, firstValuePromise } = userStatusObj; - const observable = user(auth).pipe( - switchMap((user) => { - if (!user) { - const result: SigninCheckResult = { signedIn: false, hasRequiredClaims: false, errors: {}, user: null }; - return of(result); - } else if (options && (options.hasOwnProperty('requiredClaims') || options.hasOwnProperty('validateCustomClaims'))) { - return from(user.getIdTokenResult(options?.forceRefresh ?? false)).pipe( - map((idTokenResult) => { - let validator: ClaimsValidator; - - if (options.hasOwnProperty('requiredClaims')) { - validator = getClaimsObjectValidator((options as SignInCheckOptionsClaimsObject).requiredClaims); - } else { - validator = (options as SignInCheckOptionsClaimsValidator).validateCustomClaims; - } + const needsClaimsCheck = Boolean( + options && (options.hasOwnProperty('requiredClaims') || options.hasOwnProperty('validateCustomClaims')) + ); + const forceRefresh = options?.forceRefresh ?? false; + + const computeClaimsResult = (user: User, idTokenResult: IdTokenResult): SigninCheckResult => { + let validator: ClaimsValidator; + if (options?.hasOwnProperty('requiredClaims')) { + validator = getClaimsObjectValidator((options as SignInCheckOptionsClaimsObject).requiredClaims); + } else { + validator = (options as SignInCheckOptionsClaimsValidator).validateCustomClaims; + } + const { hasRequiredClaims, errors } = validator(idTokenResult.claims); + return { signedIn: true, hasRequiredClaims, errors, user }; + }; - const { hasRequiredClaims, errors } = validator(idTokenResult.claims); + const [claimsResultState, setClaimsResultState] = React.useState<{ + user: User | null; + idTokenResult: IdTokenResult | null; + }>({ user: null, idTokenResult: null }); + + const tokenReqRef = React.useRef<{ user: User; forceRefresh: boolean; promise: InstrumentedPromise } | null>(null); + if (!hasInitialData && currentUser && needsClaimsCheck) { + const existingTokenPromise = + tokenReqRef.current && tokenReqRef.current.user === currentUser && tokenReqRef.current.forceRefresh === forceRefresh + ? tokenReqRef.current.promise + : inFlightTokenPromises.get(currentUser)?.get(forceRefresh); + if (suspenseEnabled) { + tokenReqRef.current = { + user: currentUser, + forceRefresh, + promise: existingTokenPromise ?? getOrStartTokenPromise(currentUser, forceRefresh), + }; + } else if (existingTokenPromise) { + tokenReqRef.current = { + user: currentUser, + forceRefresh, + promise: existingTokenPromise, + }; + } else { + tokenReqRef.current = null; + } + } else { + tokenReqRef.current = null; + } - const result: SigninCheckResult = { signedIn: true, hasRequiredClaims, errors, user: user }; - return result; + React.useEffect(() => { + if (!currentUser || !needsClaimsCheck) { + return; + } + let cancelled = false; + const tokenPromise = + tokenReqRef.current && tokenReqRef.current.user === currentUser && tokenReqRef.current.forceRefresh === forceRefresh + ? tokenReqRef.current.promise + : getOrStartTokenPromise(currentUser, forceRefresh); + + setTimeout(() => { + const byRefresh = inFlightTokenPromises.get(currentUser); + if (byRefresh?.get(forceRefresh) === tokenPromise) { + byRefresh.delete(forceRefresh); + } + }, 0); + + tokenPromise.then( + (idTokenResult) => { + if (!cancelled) { + setClaimsResultState({ user: currentUser, idTokenResult }); + } + }, + (error) => { + if (!cancelled) { + setClaimsResultState(() => { + throw error; + }); + } + } + ); + return () => { + cancelled = true; + }; + }, [currentUser, needsClaimsCheck, forceRefresh]); + + const cacheKey = `${needsClaimsCheck}:${forceRefresh}`; + const makeStatus = ( + statusObj: Omit, 'usable' | 'experimental_usable'> + ): ObservableStatus => + attachUsable(statusObj, (): ReactFireUsable => { + const userUsable = userStatusObj.usable as unknown as InstrumentedPromise; + let byKey = signinCheckUsableCache.get(userUsable); + if (!byKey) { + byKey = new Map(); + signinCheckUsableCache.set(userUsable, byKey); + } + let derived = byKey.get(cacheKey); + if (statusObj.status === 'success') { + if (!derived) { + derived = createFulfilledUsable(statusObj.data); + byKey.set(keyOrCacheKey(cacheKey), derived); + } else if (derived.status === 'pending') { + derived.status = 'fulfilled'; + derived.value = statusObj.data; + } else if ( + derived.status === 'fulfilled' && + (derived.value?.signedIn !== statusObj.data.signedIn || + derived.value?.user !== statusObj.data.user || + derived.value?.hasRequiredClaims !== statusObj.data.hasRequiredClaims) + ) { + derived = createFulfilledUsable(statusObj.data); + byKey.set(cacheKey, derived); + } + return derived; + } + if (!derived) { + derived = instrumentPromise( + userUsable.then((u) => { + if (!u) { + return { signedIn: false, hasRequiredClaims: false, errors: {}, user: null }; + } + if (!needsClaimsCheck) { + return { signedIn: true, hasRequiredClaims: true, errors: {}, user: u }; + } + return getOrStartTokenPromise(u, forceRefresh).then((res) => computeClaimsResult(u, res)); }) ); - } else { - // If no claims are provided to be checked, `hasRequiredClaims` is true - const result: SigninCheckResult = { signedIn: true, hasRequiredClaims: true, errors: {}, user: user }; - return of(result); + byKey.set(cacheKey, derived); } - }) - ); + return derived; + }); + + if (userStatus === 'loading' && !hasInitialData) { + return makeStatus({ + status: 'loading', + hasEmitted: false, + isComplete: false, + data: undefined as unknown as SigninCheckResult, + error: undefined, + firstValuePromise, + }); + } + + if (!currentUser) { + return makeStatus({ + status: 'success', + hasEmitted: true, + isComplete: false, + data: + hasInitialData && userStatus === 'loading' + ? (initialData as SigninCheckResult) + : { signedIn: false, hasRequiredClaims: false, errors: {}, user: null }, + error: undefined, + firstValuePromise, + }); + } + + if (!needsClaimsCheck) { + return makeStatus({ + status: 'success', + hasEmitted: true, + isComplete: false, + data: { signedIn: true, hasRequiredClaims: true, errors: {}, user: currentUser }, + error: undefined, + firstValuePromise, + }); + } + + if (suspenseEnabled && !hasInitialData && tokenReqRef.current) { + const idTokenResult = unwrapOrSuspend(tokenReqRef.current.promise); + return makeStatus({ + status: 'success', + hasEmitted: true, + isComplete: false, + data: computeClaimsResult(currentUser, idTokenResult), + error: undefined, + firstValuePromise, + }); + } + + if (!hasInitialData && tokenReqRef.current?.promise.status === 'fulfilled') { + return makeStatus({ + status: 'success', + hasEmitted: true, + isComplete: false, + data: computeClaimsResult(currentUser, tokenReqRef.current.promise.value as IdTokenResult), + error: undefined, + firstValuePromise, + }); + } + + if (claimsResultState.user === currentUser && claimsResultState.idTokenResult) { + return makeStatus({ + status: 'success', + hasEmitted: true, + isComplete: false, + data: computeClaimsResult(currentUser, claimsResultState.idTokenResult), + error: undefined, + firstValuePromise, + }); + } + + return makeStatus({ + status: hasInitialData ? 'success' : 'loading', + hasEmitted: hasInitialData, + isComplete: false, + data: initialData as SigninCheckResult, + error: undefined, + firstValuePromise, + }); +} - return useObservable(observableId, observable, options); +function keyOrCacheKey(key: string): string { + return key; } function getClaimsObjectValidator(requiredClaims: Claims): ClaimsValidator { @@ -194,7 +575,9 @@ function getClaimsObjectValidator(requiredClaims: Claims): ClaimsValidator { Object.keys(requiredClaims).forEach((claim) => { if (requiredClaims[claim] !== userClaims[claim]) { - errors[claim] = [new ReactFireError('auth/missing-claim', `Expected "${requiredClaims[claim]}", but user has "${userClaims[claim]}" instead`)]; + errors[claim] = [ + new ReactFireError('auth/missing-claim', `Expected "${requiredClaims[claim]}", but user has "${userClaims[claim]}" instead`), + ]; } }); @@ -206,31 +589,31 @@ function getClaimsObjectValidator(requiredClaims: Claims): ClaimsValidator { } /** - * @deprecated Use `useSigninCheck` instead + * @deprecated Use `` instead. Will be removed in v5.0.0. * * Conditionally render children based on [custom claims](https://firebase.google.com/docs/auth/admin/custom-claims). * * Meant for Concurrent mode only (``). [More detail](https://github.com/FirebaseExtended/reactfire/issues/325#issuecomment-827654376). */ export function ClaimsCheck({ user, fallback, children, requiredClaims }: ClaimsCheckProps) { + const suspenseMode = useSuspenseEnabledFromConfigAndContext(); + if (!suspenseMode) { + console.warn( + 'ClaimsCheck is deprecated and only works when ReactFire is in experimental Suspense Mode. Use useSigninCheck or set suspense={true} in FirebaseAppProvider if you want to use this component.' + ); + } + const { data, status, error } = useIdTokenResult(user, false); - + if (status === 'loading') { throw new Error('ClaimsCheck must be run in Suspense mode'); } else if (status === 'error') { - throw error + throw error; } const { claims } = data; const missingClaims: { [key: string]: { expected: string; actual: string | undefined } } = {}; - const suspenseMode = useSuspenseEnabledFromConfigAndContext(); - if (!suspenseMode) { - console.warn( - 'ClaimsCheck is deprecated and only works when ReactFire is in experimental Suspense Mode. Use useSigninCheck or set suspense={true} in FirebaseAppProvider if you want to use this component.' - ); - } - if (requiredClaims) { Object.keys(requiredClaims).forEach((claim) => { if (requiredClaims[claim] !== claims[claim]) { @@ -250,14 +633,10 @@ export function ClaimsCheck({ user, fallback, children, requiredClaims }: Claims } /** - * @deprecated Use `useSigninCheck` instead - * * Conditionally render children based on signed-in status and [custom claims](https://firebase.google.com/docs/auth/admin/custom-claims). - * - * Meant for Concurrent mode only (``). [More detail](https://github.com/FirebaseExtended/reactfire/issues/325#issuecomment-827654376). */ -export function AuthCheck({ fallback, children, requiredClaims }: AuthCheckProps): React.ReactElement { - const { data: user } = useUser(); +export function AuthCheck({ auth, fallback, children, requiredClaims }: AuthCheckProps): React.ReactElement { + const { data: user } = useUser(auth as Auth); const suspenseMode = useSuspenseEnabledFromConfigAndContext(); if (!suspenseMode) { diff --git a/src/database.tsx b/src/database.tsx index 5c5f998c..3008bcfb 100644 --- a/src/database.tsx +++ b/src/database.tsx @@ -1,21 +1,68 @@ -import { list, object, QueryChange, listVal, objectVal } from 'rxfire/database'; -import { ReactFireOptions, useObservable, checkIdField, ObservableStatus, ReactFireGlobals } from './index.js'; +import { + InstrumentedPromise, + ObservableStatus, + ReactFireOptions, + checkIdField, + hasInitialDataOption, + instrumentPromise, + useMappedObservableStatus, + useFirebaseStream, +} from './useFirebaseStream.js'; +import { onValue, Query as DatabaseQuery, DatabaseReference, DataSnapshot } from 'firebase/database'; -import type { Query as DatabaseQuery, DatabaseReference } from 'firebase/database'; +/** + * @deprecated Legacy rxfire change event enum. In v5.0.0, `useDatabaseObject` returns `DataSnapshot` and `useDatabaseList` returns `DataSnapshot[]`. + */ +export enum ListenEvent { + added = 'child_added', + removed = 'child_removed', + changed = 'child_changed', + moved = 'child_moved', + value = 'value', +} -// Since we're side-effect free, we need to ensure our observableId cache is global -const cachedQueries: Array = (globalThis as any as ReactFireGlobals)._reactFireDatabaseCachedQueries || []; +/** + * @deprecated Legacy rxfire change wrapper. In v5.0.0, `useDatabaseObject` returns `DataSnapshot` and `useDatabaseList` returns `DataSnapshot[]`. + */ +export interface QueryChange { + snapshot: DataSnapshot; + prevKey: string | null | undefined; + event: ListenEvent; +} -if (!(globalThis as any as ReactFireGlobals)._reactFireDatabaseCachedQueries) { - (globalThis as any as ReactFireGlobals)._reactFireDatabaseCachedQueries = cachedQueries; +function createInitialObjectPromise(ref: DatabaseReference): Promise { + return new Promise((resolve, reject) => { + onValue( + ref, + (snapshot) => { + resolve({ snapshot, prevKey: undefined, event: ListenEvent.value }); + }, + (err) => { + reject(err); + }, + { onlyOnce: true } + ); + }); } -function getUniqueIdForDatabaseQuery(query: DatabaseQuery) { - const index = cachedQueries.findIndex((cachedQuery) => cachedQuery.isEqual(query)); - if (index > -1) { - return index; +const IN_FLIGHT_EVICT_MS = 30_000; +const inFlightObjectPromises = new Map>(); + +function getOrStartInFlightObject(ref: DatabaseReference): InstrumentedPromise { + const key = ref.toString(); + let promise = inFlightObjectPromises.get(key); + if (!promise) { + promise = instrumentPromise(createInitialObjectPromise(ref)); + inFlightObjectPromises.set(key, promise); + const cleanup = () => + setTimeout(() => { + if (inFlightObjectPromises.get(key) === promise) { + inFlightObjectPromises.delete(key); + } + }, IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); } - return cachedQueries.push(query) - 1; + return promise; } /** @@ -24,19 +71,126 @@ function getUniqueIdForDatabaseQuery(query: DatabaseQuery) { * @param ref - Reference to the DB object you want to listen to * @param options */ -export function useDatabaseObject(ref: DatabaseReference, options?: ReactFireOptions): ObservableStatus { - const observableId = `database:object:${ref.toString()}`; - const observable$ = object(ref); +export function useDatabaseObject( + ref: DatabaseReference, + options?: ReactFireOptions +): ObservableStatus { + const key = ref.toString(); - return useObservable(observableId, observable$, options); + return useFirebaseStream({ + ref, + isEqual: (a, b) => a.isEqual(b), + peekInFlight: () => inFlightObjectPromises.get(key), + getOrStartInFlight: () => getOrStartInFlightObject(ref), + clearInFlight: (expectedPromise) => { + if (!expectedPromise || inFlightObjectPromises.get(key) === expectedPromise) { + inFlightObjectPromises.delete(key); + } + }, + subscribe: (onNext, onError) => { + return onValue( + ref, + (snapshot: DataSnapshot) => { + onNext({ snapshot, prevKey: undefined, event: ListenEvent.value }, false); + }, + (error: Error) => { + onError(error); + } + ); + }, + options, + }); } +function snapshotToData(snapshot: DataSnapshot, idField?: string): T { + const val = snapshot.val(); + if (!snapshot.exists() || typeof val !== 'object' || val === null || !idField) { + return val; + } + return { + ...val, + [idField]: snapshot.key, + }; +} + +/** + * Subscribe to a Realtime Database object and unwrap the snapshot into a plain object. + * + * @deprecated Use `useDatabaseObject` and read `snapshot.val()` / `snapshot.key` directly. Will be removed in v5.0.0. + */ export function useDatabaseObjectData(ref: DatabaseReference, options?: ReactFireOptions): ObservableStatus { const idField = options ? checkIdField(options) : undefined; - const observableId = `database:objectVal:${ref.toString()}:idField=${JSON.stringify(idField)}`; - const observable$ = objectVal(ref, { keyField: idField }); + const hasInitialData = hasInitialDataOption(options); + const status = useDatabaseObject(ref, { + suspense: hasInitialData ? false : options?.suspense, + }); + return useMappedObservableStatus(status, (change) => snapshotToData(change.snapshot, idField), idField ?? '', options); +} - return useObservable(observableId, observable$, options); +function snapshotToChanges(snapshot: DataSnapshot): QueryChange[] { + const changes: QueryChange[] = []; + if (snapshot.exists()) { + let prevKey: string | null = null; + snapshot.forEach((childSnapshot) => { + changes.push({ + snapshot: childSnapshot, + prevKey, + event: ListenEvent.value, + }); + prevKey = childSnapshot.key; + return false; + }); + } + return changes; +} + +function createInitialListPromise(ref: DatabaseReference | DatabaseQuery): Promise { + return new Promise((resolve, reject) => { + onValue( + ref, + (snapshot) => { + resolve(snapshotToChanges(snapshot)); + }, + (err) => { + reject(err); + }, + { onlyOnce: true } + ); + }); +} + +const inFlightListEntries: Array<{ + ref: DatabaseReference | DatabaseQuery; + promise: InstrumentedPromise; +}> = []; + +function getOrStartInFlightList(ref: DatabaseReference | DatabaseQuery): InstrumentedPromise { + const existing = inFlightListEntries.find((entry) => entry.ref.isEqual(ref)); + if (existing) { + return existing.promise; + } + const promise = instrumentPromise(createInitialListPromise(ref)); + const entry = { ref, promise }; + inFlightListEntries.push(entry); + const cleanup = () => + setTimeout(() => { + const idx = inFlightListEntries.indexOf(entry); + if (idx > -1) inFlightListEntries.splice(idx, 1); + }, IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); + return promise; +} + +function removeInFlightList( + ref: DatabaseReference | DatabaseQuery, + expectedPromise?: InstrumentedPromise +) { + const idx = inFlightListEntries.findIndex( + (entry) => entry.ref.isEqual(ref) && (!expectedPromise || entry.promise === expectedPromise) + ); + if (idx > -1) { + inFlightListEntries.splice(idx, 1); + } } /** @@ -49,18 +203,46 @@ export function useDatabaseList( ref: DatabaseReference | DatabaseQuery, options?: ReactFireOptions ): ObservableStatus { - const hash = `database:list:${getUniqueIdForDatabaseQuery(ref)}`; - const observable$ = list(ref); - - return useObservable(hash, observable$, options); + return useFirebaseStream({ + ref, + isEqual: (a, b) => a.isEqual(b), + peekInFlight: () => inFlightListEntries.find((entry) => entry.ref.isEqual(ref))?.promise, + getOrStartInFlight: () => getOrStartInFlightList(ref), + clearInFlight: (expectedPromise) => + removeInFlightList(ref, expectedPromise as InstrumentedPromise | undefined), + subscribe: (onNext, onError) => { + return onValue( + ref, + (snapshot: DataSnapshot) => { + onNext(snapshotToChanges(snapshot), false); + }, + (error: Error) => { + onError(error); + } + ); + }, + options, + }); } +/** + * Subscribe to a Realtime Database list and unwrap the snapshots into an array. + * + * @deprecated Use `useDatabaseList` and read `child.val()` / `child.key` directly. Will be removed in v5.0.0. + */ export function useDatabaseListData( ref: DatabaseReference | DatabaseQuery, options?: ReactFireOptions ): ObservableStatus { const idField = options ? checkIdField(options) : undefined; - const observableId = `database:listVal:${getUniqueIdForDatabaseQuery(ref)}:idField=${JSON.stringify(idField)}`; - const observable$ = listVal(ref, { keyField: idField }); - return useObservable(observableId, observable$, options); + const hasInitialData = hasInitialDataOption(options); + const status = useDatabaseList(ref, { + suspense: hasInitialData ? false : options?.suspense, + }); + return useMappedObservableStatus( + status, + (changes) => changes.map((change) => snapshotToData(change.snapshot, idField)), + idField ?? '', + options + ); } diff --git a/src/firebaseApp.tsx b/src/firebaseApp.tsx index 1f184b12..33b27782 100644 --- a/src/firebaseApp.tsx +++ b/src/firebaseApp.tsx @@ -11,10 +11,16 @@ const DEFAULT_APP_NAME = '[DEFAULT]'; const FirebaseAppContext = React.createContext(undefined); const SuspenseEnabledContext = React.createContext(false); +/** + * @deprecated `` is deprecated and will be removed in v5.0.0. Initialize your Firebase app at module scope (`export const app = initializeApp(config)`) and pass SDK instances or references directly to ReactFire hooks. + */ export interface FirebaseAppProviderProps { firebaseApp?: FirebaseApp; firebaseConfig?: FirebaseOptions; appName?: string; + /** + * @deprecated Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of setting `suspense={true}`. + */ suspense?: boolean; } @@ -23,10 +29,17 @@ export const version = process.env.REACTFIRE_VERSION as string; const shallowEq = (a: { [key: string]: any }, b: { [key: string]: any }) => a === b || [...Object.keys(a), ...Object.keys(b)].every((key) => a[key] === b[key]); +/** + * @deprecated `` is deprecated and will be removed in v5.0.0. Initialize your Firebase app at module scope (`export const app = initializeApp(config)`) and pass SDK instances or references directly to ReactFire hooks. + */ export function FirebaseAppProvider(props: React.PropsWithChildren) { const { firebaseConfig, appName, suspense } = props; const firebaseApp: FirebaseApp = React.useMemo(() => { + const reactVersion = React.version || 'unknown'; + registerVersion('react', reactVersion); + registerVersion('reactfire', version); + if (props.firebaseApp) { return props.firebaseApp; } @@ -45,9 +58,6 @@ export function FirebaseAppProvider(props: React.PropsWithChildren> = (globalThis as any as ReactFireGlobals)._reactFireFirestoreQueryCache || []; +const IN_FLIGHT_EVICT_MS = 30_000; +const inFlightDocPromises = new Map>>(); -if (!(globalThis as any as ReactFireGlobals)._reactFireFirestoreQueryCache) { - (globalThis as any as ReactFireGlobals)._reactFireFirestoreQueryCache = cachedQueries; +function getOrStartInFlightDoc( + key: string, + factory: () => Promise> +): InstrumentedPromise> { + let promise = inFlightDocPromises.get(key) as InstrumentedPromise> | undefined; + if (!promise) { + promise = instrumentPromise(factory()); + inFlightDocPromises.set(key, promise); + const cleanup = () => + setTimeout(() => { + if (inFlightDocPromises.get(key) === promise) { + inFlightDocPromises.delete(key); + } + }, IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); + } + return promise; } -function getUniqueIdForFirestoreQuery(query: FirestoreQuery) { - const index = cachedQueries.findIndex((cachedQuery) => queryEqual(cachedQuery, query)); - if (index > -1) { - return index; - } - return cachedQueries.push(query) - 1; +function fetchDocOnce(ref: DocumentReference): Promise> { + return getDocFromCache(ref).catch( + () => + new Promise>((resolve, reject) => { + let settled = false; + const unsubscribe = onSnapshot( + ref, + (snap) => { + if (!settled) { + settled = true; + unsubscribe?.(); + resolve(snap); + } + }, + (err) => { + if (!settled) { + settled = true; + unsubscribe?.(); + reject(err); + } + } + ); + if (settled) { + unsubscribe(); + } + }) + ); +} + +function fetchCollectionOnce(query: FirestoreQuery): Promise> { + return new Promise>((resolve, reject) => { + let settled = false; + const unsubscribe = onSnapshot( + query, + (snap) => { + if (!settled) { + settled = true; + unsubscribe?.(); + resolve(snap); + } + }, + (err) => { + if (!settled) { + settled = true; + unsubscribe?.(); + reject(err); + } + } + ); + if (settled) { + unsubscribe(); + } + }); } /** * Preload a subscription to a Firestore document reference. * * Use this to warm up `useFirestoreDoc` for a specific document + * + * @deprecated Unwrap `useFirestoreDoc(ref).experimental_usable` with `React.use()` instead. Will be removed in v5.0.0. */ export async function preloadFirestoreDoc(refProvider: () => Promise) { const ref = await refProvider(); - return preloadObservable(doc(ref), getDocObservableId(ref)); -} - -function getDocObservableId(ref: DocumentReference) { - return `firestore:doc:${ref.firestore.app.name}:${ref.path}`; + const key = `firestore:doc:${ref.firestore.app.name}:${ref.path}`; + return getOrStartInFlightDoc(key, () => fetchDocOnce(ref)); } /** @@ -39,64 +116,237 @@ function getDocObservableId(ref: DocumentReference) { * * You can preload data for this hook by calling `preloadFirestoreDoc` */ -export function useFirestoreDoc(ref: DocumentReference, options?: ReactFireOptions): ObservableStatus> { - const observableId = getDocObservableId(ref); - const observable$ = doc(ref); +export function useFirestoreDoc( + ref: DocumentReference, + options?: ReactFireOptions +): ObservableStatus> { + const once = Boolean(options?.once); + const includeMetadataChanges = options?.includeMetadataChanges; + const docKey = `firestore:${once ? 'docOnce' : 'doc'}:${ref.firestore.app.name}:${ref.path}`; + + return useFirebaseStream, DocumentSnapshot>({ + ref, + isEqual: refEqual, + peekInFlight: () => inFlightDocPromises.get(docKey) as InstrumentedPromise> | undefined, + getOrStartInFlight: () => getOrStartInFlightDoc(docKey, () => fetchDocOnce(ref)), + clearInFlight: (expectedPromise) => { + if (!expectedPromise || inFlightDocPromises.get(docKey) === expectedPromise) { + inFlightDocPromises.delete(docKey); + } + }, + subscribe: (onNext, onError, inFlightPromise) => { + if (once) { + const reqPromise = inFlightPromise ?? fetchDocOnce(ref); + reqPromise.then( + (snap) => onNext(snap, true), + (error: unknown) => onError(error) + ); + return () => {}; + } - return useObservable(observableId, observable$, options); + let active = true; + + const onSnapNext = (snap: DocumentSnapshot) => { + if (active) { + onNext(snap, false); + } + }; + const onSnapError = (error: unknown) => { + if (active) { + onError(error); + } + }; + + const unsubscribe = + includeMetadataChanges !== undefined + ? onSnapshot(ref, { includeMetadataChanges }, onSnapNext, onSnapError) + : onSnapshot(ref, onSnapNext, onSnapError); + + return () => { + active = false; + unsubscribe(); + }; + }, + options, + defaultIsComplete: once, + }); } /** * Get a firestore document and don't subscribe to changes + * + * @deprecated Pass `{ once: true }` to `useFirestoreDoc(ref, { once: true })` instead. Will be removed in v5.0.0. */ -export function useFirestoreDocOnce(ref: DocumentReference, options?: ReactFireOptions): ObservableStatus> { - const observableId = `firestore:docOnce:${ref.firestore.app.name}:${ref.path}`; - const observable$ = doc(ref).pipe(first()); +export function useFirestoreDocOnce( + ref: DocumentReference, + options?: ReactFireOptions +): ObservableStatus> { + return useFirestoreDoc(ref, { ...options, once: true }); +} - return useObservable(observableId, observable$, options); +function snapToData(snap: DocumentSnapshot, idField?: string): T | undefined { + if (!snap.exists()) { + return undefined; + } + const data = snap.data(); + if (idField && data && typeof data === 'object') { + const proto = Object.getPrototypeOf(data); + if (proto && proto !== Object.prototype) { + return Object.assign(Object.create(proto), data, { [idField]: snap.id }); + } + return { + ...data, + [idField]: snap.id, + }; + } + return data; } /** * Subscribe to Firestore Document changes and unwrap the document into a plain object + * + * @deprecated Use `useFirestoreDoc(ref)` and read `snap.data()` / `snap.id` / `snap.exists()` directly. Will be removed in v5.0.0. */ -export function useFirestoreDocData(ref: DocumentReference, options?: ReactFireOptions): ObservableStatus { +export function useFirestoreDocData( + ref: DocumentReference, + options?: ReactFireOptions +): ObservableStatus { const idField = options ? checkIdField(options) : undefined; - - const observableId = `firestore:docData:${ref.firestore.app.name}:${ref.path}:idField=${JSON.stringify(idField)}`; - const observable = docData(ref, { idField }); - - return useObservable(observableId, observable, options) as ObservableStatus; + const hasInitialData = hasInitialDataOption(options); + const docStatus = useFirestoreDoc(ref, { + suspense: hasInitialData ? false : options?.suspense, + once: options?.once, + includeMetadataChanges: options?.includeMetadataChanges, + }); + return useMappedObservableStatus(docStatus, (snap) => snapToData(snap, idField), idField ?? '', options); } /** * Get a Firestore document, unwrap the document into a plain object, and don't subscribe to changes + * + * @deprecated Use `useFirestoreDoc(ref, { once: true })` and read `snap.data()` / `snap.id` directly. Will be removed in v5.0.0. */ -export function useFirestoreDocDataOnce(ref: DocumentReference, options?: ReactFireOptions): ObservableStatus { +export function useFirestoreDocDataOnce( + ref: DocumentReference, + options?: ReactFireOptions +): ObservableStatus { const idField = options ? checkIdField(options) : undefined; + const hasInitialData = hasInitialDataOption(options); + const docStatus = useFirestoreDocOnce(ref, { suspense: hasInitialData ? false : options?.suspense }); + return useMappedObservableStatus(docStatus, (snap) => snapToData(snap, idField), idField ?? '', options); +} - const observableId = `firestore:docDataOnce:${ref.firestore.app.name}:${ref.path}:idField=${JSON.stringify(idField)}`; - const observable$ = docData(ref, { idField }).pipe(first()); +const inFlightCollectionEntries: Array<{ query: FirestoreQuery; promise: InstrumentedPromise> }> = []; - return useObservable(observableId, observable$, options) as ObservableStatus; +function getOrStartInFlightCollection( + query: FirestoreQuery, + factory: () => Promise> +): InstrumentedPromise> { + const existing = inFlightCollectionEntries.find((entry) => queryEqual(entry.query, query)); + if (existing) { + return existing.promise as InstrumentedPromise>; + } + const promise = instrumentPromise(factory()); + const entry = { query, promise }; + inFlightCollectionEntries.push(entry); + const cleanup = () => + setTimeout(() => { + const idx = inFlightCollectionEntries.indexOf(entry); + if (idx > -1) inFlightCollectionEntries.splice(idx, 1); + }, IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); + return promise; +} + +function removeInFlightCollection( + query: FirestoreQuery, + expectedPromise?: InstrumentedPromise> +) { + const idx = inFlightCollectionEntries.findIndex( + (entry) => queryEqual(entry.query, query) && (!expectedPromise || entry.promise === expectedPromise) + ); + if (idx > -1) { + inFlightCollectionEntries.splice(idx, 1); + } } /** * Subscribe to a Firestore collection */ -export function useFirestoreCollection(query: FirestoreQuery, options?: ReactFireOptions): ObservableStatus> { - const observableId = `firestore:collection:${getUniqueIdForFirestoreQuery(query)}`; - const observable$ = fromRef(query); +export function useFirestoreCollection( + query: FirestoreQuery, + options?: ReactFireOptions +): ObservableStatus> { + const once = Boolean(options?.once); + const includeMetadataChanges = options?.includeMetadataChanges; + + return useFirebaseStream, QuerySnapshot>({ + ref: query, + isEqual: queryEqual, + peekInFlight: () => + inFlightCollectionEntries.find((entry) => queryEqual(entry.query, query))?.promise as + | InstrumentedPromise> + | undefined, + getOrStartInFlight: () => getOrStartInFlightCollection(query, () => fetchCollectionOnce(query)), + clearInFlight: (expectedPromise) => removeInFlightCollection(query, expectedPromise), + subscribe: (onNext, onError, inFlightPromise) => { + if (once) { + const reqPromise = inFlightPromise ?? fetchCollectionOnce(query); + reqPromise.then( + (snap) => onNext(snap, true), + (error: unknown) => onError(error) + ); + return () => {}; + } + + let active = true; + + const onSnapNext = (snap: QuerySnapshot) => { + if (active) { + onNext(snap, false); + } + }; + const onSnapError = (error: unknown) => { + if (active) { + onError(error); + } + }; - return useObservable(observableId, observable$, options); + const unsubscribe = + includeMetadataChanges !== undefined + ? onSnapshot(query, { includeMetadataChanges }, onSnapNext, onSnapError) + : onSnapshot(query, onSnapNext, onSnapError); + + return () => { + active = false; + unsubscribe(); + }; + }, + options, + defaultIsComplete: once, + }); } /** * Subscribe to a Firestore collection and unwrap the snapshot into an array. + * + * @deprecated Use `useFirestoreCollection(query)` and map `snap.docs` directly. Will be removed in v5.0.0. */ -export function useFirestoreCollectionData(query: FirestoreQuery, options?: ReactFireOptions): ObservableStatus { +export function useFirestoreCollectionData( + query: FirestoreQuery, + options?: ReactFireOptions +): ObservableStatus { const idField = options ? checkIdField(options) : undefined; - const observableId = `firestore:collectionData:${getUniqueIdForFirestoreQuery(query)}:idField=${JSON.stringify(idField)}`; - const observable$ = collectionData(query, { idField }); - - return useObservable(observableId, observable$, options); + const hasInitialData = hasInitialDataOption(options); + const collectionStatus = useFirestoreCollection(query, { + suspense: hasInitialData ? false : options?.suspense, + once: options?.once, + includeMetadataChanges: options?.includeMetadataChanges, + }); + return useMappedObservableStatus( + collectionStatus, + (snap) => snap.docs.map((docSnap) => snapToData(docSnap, idField) as T), + idField ?? '', + options + ); } diff --git a/src/functions.tsx b/src/functions.tsx index d30be15b..ad4cd73d 100644 --- a/src/functions.tsx +++ b/src/functions.tsx @@ -1,13 +1,34 @@ -import { httpsCallable as rxHttpsCallable } from 'rxfire/functions'; -import { defer } from 'rxjs'; -import { ReactFireOptions, useObservable, ObservableStatus } from './index.js'; -import { useFunctions } from './index.js'; +import { httpsCallable } from 'firebase/functions'; +import { useFunctions } from './sdk.js'; +import { InstrumentedPromise, ObservableStatus, ReactFireOptions, instrumentPromise, useFirebaseStream } from './useFirebaseStream.js'; -import type { HttpsCallableOptions } from 'firebase/functions'; +import type { Functions, HttpsCallableOptions } from 'firebase/functions'; + +const IN_FLIGHT_EVICT_MS = 5_000; +const inFlightCallablePromises = new Map>(); + +function getOrStartCallable( + functions: Functions, + functionName: string, + callKey: string, + data?: RequestData, + httpsCallableOptions?: HttpsCallableOptions +): InstrumentedPromise { + let promise = inFlightCallablePromises.get(callKey) as InstrumentedPromise | undefined; + if (!promise) { + const callable = httpsCallable(functions, functionName, httpsCallableOptions); + promise = instrumentPromise(callable(data).then((result) => result.data)); + inFlightCallablePromises.set(callKey, promise); + const cleanup = () => setTimeout(() => inFlightCallablePromises.delete(callKey), IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); + } + return promise; +} /** * Calls a callable function. * + * @deprecated Call `httpsCallable(functions, functionName)` directly (e.g. inside React 19 `useActionState` or an event handler). Will be removed in v5.0.0. * @param functionName - The name of the function to call * @param options */ @@ -19,11 +40,41 @@ export function useCallableFunctionResponse( } ): ObservableStatus { const functions = useFunctions(); - const observableId = `functions:callableResponse:${functionName}:${JSON.stringify(options?.data)}:${JSON.stringify(options?.httpsCallableOptions)}`; - const obsFactory = rxHttpsCallable(functions, functionName, options?.httpsCallableOptions); - // Wrap in `defer` so the function is invoked lazily on subscription rather than - // on every render (each render otherwise calls the cloud function and discards it). - const observable$ = defer(() => obsFactory(options?.data)); + const callKey = `functions:callableResponse:${functions.app.name}:${functionName}:${JSON.stringify(options?.data)}:${JSON.stringify(options?.httpsCallableOptions)}`; + + return useFirebaseStream<{ functions: Functions; key: string }, ResponseData>({ + ref: { functions, key: callKey }, + isEqual: (a, b) => a.functions === b.functions && a.key === b.key, + peekInFlight: () => inFlightCallablePromises.get(callKey) as InstrumentedPromise | undefined, + getOrStartInFlight: () => + getOrStartCallable( + functions, + functionName, + callKey, + options?.data, + options?.httpsCallableOptions + ), + clearInFlight: (expectedPromise) => { + if (!expectedPromise || inFlightCallablePromises.get(callKey) === expectedPromise) { + inFlightCallablePromises.delete(callKey); + } + }, + subscribe: (onNext, onError, inFlightPromise) => { + const reqPromise = + inFlightPromise ?? + httpsCallable( + functions, + functionName, + options?.httpsCallableOptions + )(options?.data).then((result) => result.data); - return useObservable(observableId, observable$, options); + reqPromise.then( + (value) => onNext(value, true), + (error: unknown) => onError(error) + ); + return () => {}; + }, + options, + defaultIsComplete: true, + }); } diff --git a/src/index.ts b/src/index.ts index 028a4a62..364a3fc4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,50 +3,24 @@ import { SuspenseSubject } from './SuspenseSubject.js'; import type { Query as FirestoreQuery } from 'firebase/firestore'; import type { Query as DatabaseQuery } from 'firebase/database'; +/** + * @deprecated Internal global cache type. Will be removed in v5.0.0. + */ export type ReactFireGlobals = { _reactFireDatabaseCachedQueries: Array; _reactFireFirestoreQueryCache: Array; _reactFirePreloadedObservables: Map>; }; -export class ReactFireError extends Error { - readonly name = 'ReactFireError'; - - constructor(readonly code: string, message: string, public customData?: Record) { - super(message); - - // Fix For ES5 - // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, ReactFireError.prototype); - } -} - -export interface ReactFireOptions { - idField?: string; - initialData?: T | any; - /** - * @deprecated use initialData instead - */ - startWithValue?: T | any; - suspense?: boolean; -} - -export function checkOptions(options: ReactFireOptions, field: string) { - // make sure the field passed in is a valid key of ReactFire Options - if (field === 'idField' || field === 'initialData' || field === 'suspense') { - return options ? (options[field] as ReactFireOptions['idField'] | ReactFireOptions['initialData'] | ReactFireOptions['suspense']) : undefined; - } - - throw new Error(`Field "${field}" is not a valid key in ReactFireOptions`); -} - -export function checkinitialData(options: ReactFireOptions) { - return checkOptions(options, 'initialData'); -} - -export function checkIdField(options: ReactFireOptions) { - return checkOptions(options, 'idField'); -} +export { + ReactFireError, + ReactFireOptions, + ReactFireUsable, + ObservableStatus, + checkOptions, + checkinitialData, + checkIdField, +} from './useFirebaseStream.js'; export * from './auth.js'; export * from './database.js'; diff --git a/src/performance.tsx b/src/performance.tsx index 7f444bae..0308fb31 100644 --- a/src/performance.tsx +++ b/src/performance.tsx @@ -1,31 +1,60 @@ import * as React from 'react'; +import { trace } from 'firebase/performance'; +import type { FirebasePerformance } from 'firebase/performance'; +import { PerformanceSdkContext } from './sdk.js'; export interface SuspensePerfProps { children: React.ReactNode; traceId: string; fallback: React.ReactNode; + /** + * Optional Firebase Performance Monitoring instance to record the trace with. + */ + perf?: FirebasePerformance; } -export function SuspenseWithPerf({ children, traceId, fallback }: SuspensePerfProps): React.ReactElement { - // TODO: Should this import firebase/performance? +function TraceWhileMounted({ + perf, + traceId, + children, +}: { + perf?: FirebasePerformance; + traceId: string; + children: React.ReactNode; +}): React.ReactElement { + React.useEffect(() => { + const perfTrace = perf ? trace(perf, traceId) : undefined; + perfTrace?.start(); - const perf = typeof globalThis !== 'undefined' ? globalThis.performance : undefined; - const entries = perf?.getEntriesByName?.(traceId, 'measure') || []; - const startMarkName = `_${traceId}Start[${entries.length}]`; - const endMarkName = `_${traceId}End[${entries.length}]`; + const browserPerf = typeof globalThis !== 'undefined' ? globalThis.performance : undefined; + const entries = browserPerf?.getEntriesByName?.(traceId, 'measure') || []; + const startMarkName = `_${traceId}Start[${entries.length}]`; + const endMarkName = `_${traceId}End[${entries.length}]`; + browserPerf?.mark?.(startMarkName); - const Fallback = () => { - React.useEffect(() => { - perf?.mark?.(startMarkName); + return () => { + perfTrace?.stop(); + browserPerf?.mark?.(endMarkName); + browserPerf?.measure?.(traceId, startMarkName, endMarkName); + }; + }, [perf, traceId]); - return () => { - perf?.mark?.(endMarkName); - perf?.measure?.(traceId, startMarkName, endMarkName); - }; - }, []); + return <>{children}; +} - return <>{fallback}; - }; +export function SuspenseWithPerf({ children, traceId, fallback, perf }: SuspensePerfProps): React.ReactElement { + const contextPerf = React.useContext(PerformanceSdkContext); + const resolvedPerf = perf ?? contextPerf; - return }>{children}; + return ( + + {fallback} + + } + > + {children} + + ); } diff --git a/src/remote-config.tsx b/src/remote-config.tsx index 83b398a0..2b730379 100644 --- a/src/remote-config.tsx +++ b/src/remote-config.tsx @@ -1,11 +1,50 @@ -import { useRemoteConfig } from './index.js'; -import { useObservable, ObservableStatus } from './useObservable.js'; -import { getValue, getString, getBoolean, getNumber, getAll, AllParameters } from 'rxfire/remote-config'; -import { Observable } from 'rxjs'; +import { useRemoteConfig } from './sdk.js'; +import { InstrumentedPromise, ObservableStatus, instrumentPromise, useFirebaseStream } from './useFirebaseStream.js'; +import { + ensureInitialized, + getValue, + getString, + getBoolean, + getNumber, + getAll, + RemoteConfig, + Value as RemoteConfigValue, +} from 'firebase/remote-config'; -import type { RemoteConfig, Value as RemoteConfigValue } from 'firebase/remote-config'; +/** + * @deprecated Use `getAll(remoteConfig)` from `firebase/remote-config` directly. Will be removed in v5.0.0. + */ +export type AllParameters = { + [key: string]: RemoteConfigValue; +}; + +type Getter = (remoteConfig: RemoteConfig, key: string) => T; -type Getter$ = (remoteConfig: RemoteConfig, key: string) => Observable; +const initializedRemoteConfigs = new WeakSet(); +const inFlightRemoteConfigValues = new WeakMap>>(); + +function getOrStartRemoteConfigValue( + remoteConfig: RemoteConfig, + key: string, + getter: Getter +): InstrumentedPromise { + let byKey = inFlightRemoteConfigValues.get(remoteConfig); + if (!byKey) { + byKey = new Map(); + inFlightRemoteConfigValues.set(remoteConfig, byKey); + } + let promise = byKey.get(key) as InstrumentedPromise | undefined; + if (!promise) { + promise = instrumentPromise( + ensureInitialized(remoteConfig).then(() => { + initializedRemoteConfigs.add(remoteConfig); + return getter(remoteConfig, key); + }) + ); + byKey.set(key, promise); + } + return promise; +} /** * Helper function to construct type safe functions. Since Remote Config has @@ -15,20 +54,47 @@ type Getter$ = (remoteConfig: RemoteConfig, key: string) => Observable; * @param getter * @param remoteConfig */ -function useRemoteConfigValue_INTERNAL(key: string, getter: Getter$): ObservableStatus { +function useRemoteConfigValue_INTERNAL(key: string, getter: Getter): ObservableStatus { const remoteConfig = useRemoteConfig(); - const appName = remoteConfig.app.name; - const $value = getter(remoteConfig, key); - - const observableId = `remoteConfig:${key}:${getter.name}:${appName}`; - return useObservable(observableId, $value); + return useFirebaseStream<{ remoteConfig: RemoteConfig; key: string }, T>({ + ref: { remoteConfig, key }, + isEqual: (a, b) => a.remoteConfig === b.remoteConfig && a.key === b.key, + peekInFlight: () => inFlightRemoteConfigValues.get(remoteConfig)?.get(key), + getOrStartInFlight: () => getOrStartRemoteConfigValue(remoteConfig, key, getter), + clearInFlight: (expectedPromise) => { + const byKey = inFlightRemoteConfigValues.get(remoteConfig); + if (byKey && (!expectedPromise || byKey.get(key) === expectedPromise)) { + byKey.delete(key); + } + }, + getSyncInitialValue: () => + initializedRemoteConfigs.has(remoteConfig) + ? { hasValue: true, value: getter(remoteConfig, key), isComplete: true } + : { hasValue: false, value: undefined as unknown as T }, + subscribe: (onNext, onError) => { + if (initializedRemoteConfigs.has(remoteConfig)) { + onNext(getter(remoteConfig, key), true); + return () => {}; + } + ensureInitialized(remoteConfig).then( + () => { + initializedRemoteConfigs.add(remoteConfig); + onNext(getter(remoteConfig, key), true); + }, + (err) => onError(err) + ); + return () => {}; + }, + defaultIsComplete: true, + }); } /** * Accepts a key and optionally a Remote Config instance. Returns a * Remote Config Value. * + * @deprecated Call `fetchAndActivate(remoteConfig)` and synchronous getters (`getValue(remoteConfig, key)`) from `firebase/remote-config` directly. Will be removed in v5.0.0. * @param key The parameter key in Remote Config */ export function useRemoteConfigValue(key: string): ObservableStatus { @@ -37,6 +103,8 @@ export function useRemoteConfigValue(key: string): ObservableStatus { @@ -45,6 +113,8 @@ export function useRemoteConfigString(key: string): ObservableStatus { /** * Convience method similar to useRemoteConfigValue. Returns a `number` from a Remote Config parameter. + * + * @deprecated Call `fetchAndActivate(remoteConfig)` and `getNumber(remoteConfig, key)` from `firebase/remote-config` directly. Will be removed in v5.0.0. * @param key The parameter key in Remote Config */ export function useRemoteConfigNumber(key: string): ObservableStatus { @@ -53,6 +123,8 @@ export function useRemoteConfigNumber(key: string): ObservableStatus { /** * Convience method similar to useRemoteConfigValue. Returns a `boolean` from a Remote Config parameter. + * + * @deprecated Call `fetchAndActivate(remoteConfig)` and `getBoolean(remoteConfig, key)` from `firebase/remote-config` directly. Will be removed in v5.0.0. * @param key The parameter key in Remote Config */ export function useRemoteConfigBoolean(key: string): ObservableStatus { @@ -61,8 +133,10 @@ export function useRemoteConfigBoolean(key: string): ObservableStatus { /** * Convience method similar to useRemoteConfigValue. Returns allRemote Config parameters. + * + * @deprecated Call `fetchAndActivate(remoteConfig)` and `getAll(remoteConfig)` from `firebase/remote-config` directly. Will be removed in v5.0.0. * @param key The parameter key in Remote Config */ export function useRemoteConfigAll(key: string): ObservableStatus { - return useRemoteConfigValue_INTERNAL(key, getAll); + return useRemoteConfigValue_INTERNAL(key, (rc) => getAll(rc)); } diff --git a/src/sdk.tsx b/src/sdk.tsx index c3238604..19301dcb 100644 --- a/src/sdk.tsx +++ b/src/sdk.tsx @@ -11,18 +11,25 @@ import type { FirebaseStorage } from 'firebase/storage'; import type { RemoteConfig } from 'firebase/remote-config'; import { useFirebaseApp } from './firebaseApp.js'; import { FirebaseApp } from 'firebase/app'; -import { ObservableStatus, useObservable } from './useObservable.js'; -import { from } from 'rxjs'; -import { ReactFireOptions } from './index.js'; +import { InstrumentedPromise, ObservableStatus, ReactFireOptions, instrumentPromise, useFirebaseStream } from './useFirebaseStream.js'; +/** @deprecated Pass Firebase SDK instances or references directly instead of using Context Providers. Will be removed in v5.0.0. */ export const AppCheckSdkContext = React.createContext(undefined); +/** @deprecated Pass `auth` directly to `useUser(auth)`, `useIdTokenResult(auth)`, or ``. Will be removed in v5.0.0. */ export const AuthSdkContext = React.createContext(undefined); +/** @deprecated Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. */ export const AnalyticsSdkContext = React.createContext(undefined); +/** @deprecated Pass Realtime Database references directly to `useDatabaseObject` / `useDatabaseList`. Will be removed in v5.0.0. */ export const DatabaseSdkContext = React.createContext(undefined); +/** @deprecated Pass Firestore references directly to `useFirestoreDoc` / `useFirestoreCollection`. Will be removed in v5.0.0. */ export const FirestoreSdkContext = React.createContext(undefined); +/** @deprecated Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. */ export const FunctionsSdkContext = React.createContext(undefined); +/** @deprecated Pass Storage references directly to `useStorageDownloadURL`. Will be removed in v5.0.0. */ export const StorageSdkContext = React.createContext(undefined); +/** @deprecated Pass `perf` directly to ``. Will be removed in v5.0.0. */ export const PerformanceSdkContext = React.createContext(undefined); +/** @deprecated Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. */ export const RemoteConfigSdkContext = React.createContext(undefined); type FirebaseSdks = Analytics | AppCheck | Auth | Database | Firestore | FirebasePerformance | FirebaseStorage | Functions | RemoteConfig; @@ -49,6 +56,8 @@ function useSdk(SdkContext: React.Context>(); + function useInitSdk( sdkName: string, SdkContext: React.Context, @@ -64,29 +73,74 @@ function useInitSdk( throw new Error(`Cannot initialize SDK ${sdkName} because it already exists in Context`); } - const initializeSdk = React.useMemo(() => sdkInitializer(firebaseApp), [firebaseApp]); + const key = `firebase-sdk:${sdkName}:${firebaseApp.name}`; + const getOrStart = () => { + let p = inFlightInitSdk.get(key) as InstrumentedPromise | undefined; + if (!p) { + p = instrumentPromise(sdkInitializer(firebaseApp)); + inFlightInitSdk.set(key, p); + } + return p; + }; - return useObservable(`firebase-sdk:${sdkName}:${firebaseApp.name}`, from(initializeSdk), options); + return useFirebaseStream({ + ref: key, + peekInFlight: () => inFlightInitSdk.get(key) as InstrumentedPromise | undefined, + getOrStartInFlight: getOrStart, + clearInFlight: (expected) => { + if (!expected || inFlightInitSdk.get(key) === expected) { + inFlightInitSdk.delete(key); + } + }, + subscribe: (onNext, onError, inFlightPromise) => { + const p = inFlightPromise ?? getOrStart(); + p.then( + (sdk) => onNext(sdk, true), + (err) => onError(err) + ); + return () => {}; + }, + options, + defaultIsComplete: true, + }); } +/** @deprecated Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. */ export const AppCheckProvider = getSdkProvider(AppCheckSdkContext); +/** @deprecated Pass `auth` directly to `useUser(auth)`, `useIdTokenResult(auth)`, or ``. Will be removed in v5.0.0. */ export const AuthProvider = getSdkProvider(AuthSdkContext); +/** @deprecated Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. */ export const AnalyticsProvider = getSdkProvider(AnalyticsSdkContext); +/** @deprecated Pass Realtime Database references directly to `useDatabaseObject` / `useDatabaseList`. Will be removed in v5.0.0. */ export const DatabaseProvider = getSdkProvider(DatabaseSdkContext); +/** @deprecated Pass Firestore references directly to `useFirestoreDoc` / `useFirestoreCollection`. Will be removed in v5.0.0. */ export const FirestoreProvider = getSdkProvider(FirestoreSdkContext); +/** @deprecated Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. */ export const FunctionsProvider = getSdkProvider(FunctionsSdkContext); +/** @deprecated Pass `perf` directly to ``. Will be removed in v5.0.0. */ export const PerformanceProvider = getSdkProvider(PerformanceSdkContext); +/** @deprecated Pass Storage references directly to `useStorageDownloadURL`. Will be removed in v5.0.0. */ export const StorageProvider = getSdkProvider(StorageSdkContext); +/** @deprecated Pass Firebase SDK instances directly instead of using Context Providers. Will be removed in v5.0.0. */ export const RemoteConfigProvider = getSdkProvider(RemoteConfigSdkContext); +/** @deprecated Initialize AppCheck at module scope instead of reading from Context. Will be removed in v5.0.0. */ export const useAppCheck = () => useSdk(AppCheckSdkContext); +/** @deprecated Initialize Auth at module scope (`getAuth(app)`) instead of reading from Context. Will be removed in v5.0.0. */ export const useAuth = () => useSdk(AuthSdkContext); +/** @deprecated Initialize Analytics at module scope instead of reading from Context. Will be removed in v5.0.0. */ export const useAnalytics = () => useSdk(AnalyticsSdkContext); +/** @deprecated Initialize Database at module scope (`getDatabase(app)`) instead of reading from Context. Will be removed in v5.0.0. */ export const useDatabase = () => useSdk(DatabaseSdkContext); +/** @deprecated Initialize Firestore at module scope (`getFirestore(app)`) instead of reading from Context. Will be removed in v5.0.0. */ export const useFirestore = () => useSdk(FirestoreSdkContext); +/** @deprecated Initialize Functions at module scope (`getFunctions(app)`) instead of reading from Context. Will be removed in v5.0.0. */ export const useFunctions = () => useSdk(FunctionsSdkContext); +/** @deprecated Initialize Performance at module scope (`getPerformance(app)`) instead of reading from Context. Will be removed in v5.0.0. */ export const usePerformance = () => useSdk(PerformanceSdkContext); +/** @deprecated Initialize Storage at module scope (`getStorage(app)`) instead of reading from Context. Will be removed in v5.0.0. */ export const useStorage = () => useSdk(StorageSdkContext); +/** @deprecated Initialize RemoteConfig at module scope (`getRemoteConfig(app)`) instead of reading from Context. Will be removed in v5.0.0. */ export const useRemoteConfig = () => useSdk(RemoteConfigSdkContext); type InitSdkHook = ( @@ -94,15 +148,24 @@ type InitSdkHook = ( options?: ReactFireOptions ) => ObservableStatus; +/** @deprecated Initialize Firebase SDKs at module scope (`initializeFirestore`, etc.) instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitAppCheck: InitSdkHook = (initializer, options) => useInitSdk('appcheck', AppCheckSdkContext, initializer, options); +/** @deprecated Initialize Firebase SDKs at module scope (`initializeAuth`, etc.) instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitAuth: InitSdkHook = (initializer, options) => useInitSdk('auth', AuthSdkContext, initializer, options); +/** @deprecated Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitAnalytics: InitSdkHook = (initializer, options) => useInitSdk('analytics', AnalyticsSdkContext, initializer, options); +/** @deprecated Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitDatabase: InitSdkHook = (initializer, options) => useInitSdk('database', DatabaseSdkContext, initializer, options); +/** @deprecated Initialize Firebase SDKs at module scope (`initializeFirestore`) instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitFirestore: InitSdkHook = (initializer, options) => useInitSdk('firestore', FirestoreSdkContext, initializer, options); +/** @deprecated Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitFunctions: InitSdkHook = (initializer, options) => useInitSdk('functions', FunctionsSdkContext, initializer, options); +/** @deprecated Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitPerformance: InitSdkHook = (initializer, options) => useInitSdk('performance', PerformanceSdkContext, initializer, options); +/** @deprecated Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitRemoteConfig: InitSdkHook = (initializer, options) => useInitSdk('remoteconfig', RemoteConfigSdkContext, initializer, options); +/** @deprecated Initialize Firebase SDKs at module scope instead of `useInit*`. Will be removed in v5.0.0. */ export const useInitStorage: InitSdkHook = (initializer, options) => useInitSdk('storage', StorageSdkContext, initializer, options); diff --git a/src/storage.tsx b/src/storage.tsx index 6bb1ccb3..a225ea71 100644 --- a/src/storage.tsx +++ b/src/storage.tsx @@ -1,24 +1,128 @@ import * as React from 'react'; -import { getDownloadURL, fromTask } from 'rxfire/storage'; -import { defer } from 'rxjs'; -import { ReactFireOptions, useObservable, ObservableStatus, useStorage } from './index.js'; +import { useStorage } from './sdk.js'; import { useSuspenseEnabledFromConfigAndContext } from './firebaseApp.js'; -import { ref } from 'firebase/storage'; +import { InstrumentedPromise, ObservableStatus, ReactFireOptions, instrumentPromise, useFirebaseStream } from './useFirebaseStream.js'; +import { getDownloadURL, ref } from 'firebase/storage'; import type { UploadTask, UploadTaskSnapshot, StorageReference, FirebaseStorage } from 'firebase/storage'; +const IN_FLIGHT_EVICT_MS = 5_000; +const inFlightStorageTasks = new WeakMap>(); + +function getOrStartStorageTask(task: UploadTask): InstrumentedPromise { + let promise = inFlightStorageTasks.get(task); + if (!promise) { + promise = instrumentPromise( + new Promise((resolve, reject) => { + let settled = false; + const timer = setTimeout(() => { + if (!settled) { + settled = true; + unsub?.(); + resolve(task.snapshot); + } + }, 0); + const unsub = task.on( + 'state_changed', + (snap) => { + if (!settled) { + settled = true; + clearTimeout(timer); + unsub?.(); + resolve(snap); + } + }, + (err) => { + if (!settled) { + settled = true; + clearTimeout(timer); + unsub?.(); + reject(err); + } + } + ); + if (settled) { + unsub(); + } + }) + ); + inFlightStorageTasks.set(task, promise); + const cleanup = () => setTimeout(() => inFlightStorageTasks.delete(task), IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); + } + return promise; +} + /** * Subscribe to the progress of a storage task * + * @deprecated Track `uploadBytesResumable` progress inside your upload handler or action instead. Will be removed in v5.0.0. * @param task - the task you want to listen to * @param ref - reference to the blob the task is acting on * @param options */ -export function useStorageTask(task: UploadTask, ref: StorageReference, options?: ReactFireOptions): ObservableStatus { - const observableId = `storage:task:${ref.toString()}`; - const observable$ = fromTask(task); +export function useStorageTask( + task: UploadTask, + ref: StorageReference, + options?: ReactFireOptions +): ObservableStatus { + const taskKey = `storage:task:${ref.toString()}`; + + return useFirebaseStream<{ task: UploadTask; key: string }, UploadTaskSnapshot | T>({ + ref: { task, key: taskKey }, + isEqual: (a, b) => a.task === b.task && a.key === b.key, + peekInFlight: () => inFlightStorageTasks.get(task), + getOrStartInFlight: () => getOrStartStorageTask(task), + clearInFlight: () => inFlightStorageTasks.delete(task), + subscribe: (onNext, onError) => { + let emitted = false; + let completed = false; + const initialTimer = setTimeout(() => { + if (!emitted) { + emitted = true; + onNext(task.snapshot, completed); + } + }, 0); + + const unsubscribe = task.on( + 'state_changed', + (snap) => { + emitted = true; + clearTimeout(initialTimer); + onNext(snap, completed); + }, + (error: unknown) => { + clearTimeout(initialTimer); + onError(error); + }, + () => { + emitted = true; + completed = true; + clearTimeout(initialTimer); + onNext(task.snapshot, true); + } + ); - return useObservable(observableId, observable$, options); + return () => { + clearTimeout(initialTimer); + unsubscribe(); + }; + }, + options, + }); +} + +const inFlightDownloadUrls = new Map>(); + +function getOrStartDownloadUrl(key: string, storageRef: StorageReference): InstrumentedPromise { + let promise = inFlightDownloadUrls.get(key); + if (!promise) { + promise = instrumentPromise(getDownloadURL(storageRef)); + inFlightDownloadUrls.set(key, promise); + const cleanup = () => setTimeout(() => inFlightDownloadUrls.delete(key), IN_FLIGHT_EVICT_MS); + promise.then(cleanup, cleanup); + } + return promise; } /** @@ -27,16 +131,37 @@ export function useStorageTask(task: UploadTask, ref: StorageRefere * @param ref - reference to the blob you want to download * @param options */ -export function useStorageDownloadURL(ref: StorageReference, options?: ReactFireOptions): ObservableStatus { - const observableId = `storage:downloadUrl:${ref.toString()}`; - // Wrap in `defer` so the download URL request is created lazily on subscription - // rather than eagerly on every render (which fires a discarded request each time, - // and an unhandled rejection when the object is missing). - const observable$ = defer(() => getDownloadURL(ref)); - - return useObservable(observableId, observable$, options); +export function useStorageDownloadURL( + storageRef: StorageReference, + options?: ReactFireOptions +): ObservableStatus { + const urlKey = `storage:downloadUrl:${storageRef.toString()}`; + + return useFirebaseStream({ + ref: urlKey, + peekInFlight: () => inFlightDownloadUrls.get(urlKey), + getOrStartInFlight: () => getOrStartDownloadUrl(urlKey, storageRef), + clearInFlight: (expectedPromise) => { + if (!expectedPromise || inFlightDownloadUrls.get(urlKey) === expectedPromise) { + inFlightDownloadUrls.delete(urlKey); + } + }, + subscribe: (onNext, onError, inFlightPromise) => { + const reqPromise = (inFlightPromise as Promise | undefined) ?? getDownloadURL(storageRef); + reqPromise.then( + (url) => onNext(url, true), + (error: unknown) => onError(error) + ); + return () => {}; + }, + options, + defaultIsComplete: true, + }); } +/** + * @deprecated Use `useStorageDownloadURL(ref)` with a standard `` element instead. Will be removed in v5.0.0. + */ export type StorageImageProps = { storagePath: string; storage?: FirebaseStorage; @@ -44,7 +169,9 @@ export type StorageImageProps = { placeHolder?: React.ReactNode; }; -function StorageFromContext(props: StorageImageProps & React.DetailedHTMLProps, HTMLImageElement>) { +function StorageFromContext( + props: StorageImageProps & React.DetailedHTMLProps, HTMLImageElement> +) { const storage = useStorage(); props = { ...props, storage }; @@ -52,7 +179,9 @@ function StorageFromContext(props: StorageImageProps & React.DetailedHTMLProps; } -function INTERNALStorageImage(props: StorageImageProps & React.DetailedHTMLProps, HTMLImageElement>): React.ReactNode { +function INTERNALStorageImage( + props: StorageImageProps & React.DetailedHTMLProps, HTMLImageElement> +): React.ReactNode { const { storage, storagePath, suspense, placeHolder, ...imgProps } = props; const reactfireOptions: ReactFireOptions = { @@ -79,7 +208,12 @@ function INTERNALStorageImage(props: StorageImageProps & React.DetailedHTMLProps } } -export function StorageImage(props: StorageImageProps & React.DetailedHTMLProps, HTMLImageElement>) { +/** + * @deprecated Use `useStorageDownloadURL(ref)` with a standard `` element instead. Will be removed in v5.0.0. + */ +export function StorageImage( + props: StorageImageProps & React.DetailedHTMLProps, HTMLImageElement> +) { const { storage } = props; if (storage) { diff --git a/src/useFirebaseStream.ts b/src/useFirebaseStream.ts new file mode 100644 index 00000000..cb0b9edc --- /dev/null +++ b/src/useFirebaseStream.ts @@ -0,0 +1,566 @@ +import * as React from 'react'; +import { registerVersion } from 'firebase/app'; +import { useSuspenseEnabledFromConfigAndContext, version } from './firebaseApp.js'; + +/** + * A thenable compatible with React 19's `React.use()` while remaining structurally valid + * under `@types/react` 16, 17, 18, and 19. + */ +export type ReactFireUsable = Promise; + +export type InstrumentedPromise = Promise & { + status?: 'pending' | 'fulfilled' | 'rejected'; + value?: T; + reason?: unknown; +}; + +/** + * @deprecated Error class used only by the deprecated `useSigninCheck` hook. Will be removed in v5.0.0. + */ +export class ReactFireError extends Error { + readonly name = 'ReactFireError'; + + constructor(readonly code: string, message: string, public customData?: Record) { + super(message); + + // Fix For ES5 + // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, ReactFireError.prototype); + } +} + +export interface ReactFireOptions { + /** + * @deprecated `*Data` hooks and `idField` are deprecated and will be removed in v5.0.0. Use snapshot hooks (`useFirestoreDoc`, `useFirestoreCollection`, `useDatabaseObject`, `useDatabaseList`) and read `doc.id` or `child.key` directly. + */ + idField?: string; + initialData?: T | any; + /** + * @deprecated use `initialData` instead + */ + startWithValue?: T | any; + /** + * @deprecated Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead of setting `suspense: true`. + */ + suspense?: boolean; + /** + * Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). + */ + once?: boolean; + /** + * Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). + */ + includeMetadataChanges?: boolean; +} + +/** + * @deprecated Internal option helper. Will be removed in v5.0.0. + */ +export function checkOptions(options: ReactFireOptions, field: string) { + // make sure the field passed in is a valid key of ReactFire Options + if (field === 'idField' || field === 'initialData' || field === 'suspense') { + return options ? (options[field] as ReactFireOptions['idField'] | ReactFireOptions['initialData'] | ReactFireOptions['suspense']) : undefined; + } + + throw new Error(`Field "${field}" is not a valid key in ReactFireOptions`); +} + +/** + * @deprecated Internal option helper. Will be removed in v5.0.0. + */ +export function checkinitialData(options: ReactFireOptions) { + return checkOptions(options, 'initialData'); +} + +/** + * @deprecated Internal option helper. Will be removed in v5.0.0. + */ +export function checkIdField(options: ReactFireOptions) { + return checkOptions(options, 'idField'); +} + +export interface ObservableStatus { + /** + * The loading status. + * + * - `loading`: Waiting for the first value from an observable + * - `error`: Something went wrong. Check `ObservableStatus.error` for more details + * - `success`: The hook has emitted at least one value + * + * If `initialData` is passed in, this will skip `loading` and go straight to `success`. + */ + status: 'loading' | 'error' | 'success'; + /** + * Indicates whether the hook has emitted a value at some point + * + * If `initialData` is passed in, this will be `true`. + * + * @deprecated Check `status === 'success'` or pass `.experimental_usable` to `React.use()` instead. Will be removed in v5.0.0. + */ + hasEmitted: boolean; // has received at least one value + /** + * If this is `true`, the hook will be emitting no further items. + * + * @deprecated Will be removed in v5.0.0. + */ + isComplete: boolean; + /** + * The most recent value. + * + * If `initialData` is passed in, the first value of `data` will be the value provided in `initialData` **UNLESS** the underlying observable is ready, in which case it will skip `initialData`. + */ + data: T; + /** + * Any error that may have occurred in the underlying observable + */ + error: Error | undefined; + /** + * Promise that resolves after first emit from observable + * + * @deprecated Pass `.experimental_usable` (or `.usable`) to `React.use()` instead. Will be removed in v5.0.0. + */ + firstValuePromise: Promise; + /** + * A React 19 `Usable` that can be passed directly into `React.use()` + * (`const data = use(useFirestoreDoc(ref).usable)`). + * + * Suspends on initial load when no data is available yet, and synchronously + * unwraps the latest live value on all subsequent renders without re-suspending. + */ + readonly usable: ReactFireUsable; + /** + * A React 19 `Usable` that can be passed directly into `React.use()` + * (`const data = use(useFirestoreDoc(ref).experimental_usable)`). + * + * Suspends on initial load when no data is available yet, and synchronously + * unwraps the latest live value on all subsequent renders without re-suspending. + */ + readonly experimental_usable: ReactFireUsable; +} + +export function instrumentPromise(promise: Promise): InstrumentedPromise { + const instrumented = promise as InstrumentedPromise; + if (!instrumented.status) { + instrumented.status = 'pending'; + instrumented.then( + (value) => { + instrumented.status = 'fulfilled'; + instrumented.value = value; + }, + (reason) => { + instrumented.status = 'rejected'; + instrumented.reason = reason; + } + ); + } + return instrumented; +} + +export function createFulfilledUsable(value: T): InstrumentedPromise { + const p = Promise.resolve(value) as InstrumentedPromise; + p.status = 'fulfilled'; + p.value = value; + return p; +} + +export function unwrapOrSuspend(promise: InstrumentedPromise): T { + if (promise.status === 'fulfilled') { + return promise.value as T; + } + if (promise.status === 'rejected') { + throw promise.reason; + } + throw promise; +} + +export function attachUsable( + target: Omit, 'usable' | 'experimental_usable'>, + getUsable: () => ReactFireUsable +): ObservableStatus { + Object.defineProperties(target, { + usable: { + get: getUsable, + enumerable: false, + configurable: true, + }, + experimental_usable: { + get: getUsable, + enumerable: false, + configurable: true, + }, + }); + return target as ObservableStatus; +} + +export function hasInitialDataOption(options?: ReactFireOptions): boolean { + return Boolean(options && ('initialData' in options || 'startWithValue' in options)); +} + +export function getInitialDataOption(options?: ReactFireOptions): T | undefined { + return (options?.initialData ?? options?.startWithValue) as T | undefined; +} + +const derivedUsableCache = new WeakMap>>(); +const derivedValueCache = new WeakMap>(); + +function projectCached(val: S, project: (val: S) => T, key: string): T { + if (val !== null && typeof val === 'object') { + let byKey = derivedValueCache.get(val as unknown as object); + if (!byKey) { + byKey = new Map(); + derivedValueCache.set(val as unknown as object, byKey); + } + const existing = byKey.get(key); + if (existing) { + return existing.value as T; + } + const computed = project(val); + byKey.set(key, { value: computed }); + return computed; + } + return project(val); +} + +export function useMappedObservableStatus( + source: ObservableStatus, + project: (val: S) => T, + key = '', + options?: ReactFireOptions +): ObservableStatus { + const hasInitialData = hasInitialDataOption(options); + const initialData = getInitialDataOption(options); + const initialUsableRef = React.useRef<{ value: T; promise: InstrumentedPromise } | null>(null); + const memoRef = React.useRef<{ sourceData: S; key: string; mappedData: T } | null>(null); + + if (source.status === 'success') { + if ( + !memoRef.current || + !Object.is(memoRef.current.sourceData, source.data) || + memoRef.current.key !== key + ) { + memoRef.current = { + sourceData: source.data, + key, + mappedData: projectCached(source.data, project, key), + }; + } + const mappedData = memoRef.current.mappedData; + return attachUsable( + { + ...source, + data: mappedData, + }, + () => { + const sourceUsable = source.usable as unknown as InstrumentedPromise; + let byKey = derivedUsableCache.get(sourceUsable); + if (!byKey) { + byKey = new Map(); + derivedUsableCache.set(sourceUsable, byKey); + } + let derived = byKey.get(key) as InstrumentedPromise | undefined; + if (!derived) { + derived = createFulfilledUsable(mappedData); + byKey.set(key, derived); + } else if (derived.status === 'pending') { + derived.status = 'fulfilled'; + derived.value = mappedData; + } + return derived; + } + ); + } + + if (hasInitialData) { + return attachUsable( + { + ...source, + status: 'success', + hasEmitted: true, + firstValuePromise: Promise.resolve(), + data: initialData as T, + }, + () => { + const val = initialData as T; + if (!initialUsableRef.current || !Object.is(initialUsableRef.current.value, val)) { + initialUsableRef.current = { + value: val, + promise: createFulfilledUsable(val), + }; + } + return initialUsableRef.current.promise; + } + ); + } + + return attachUsable( + { + ...source, + data: undefined as unknown as T, + }, + () => { + const sourceUsable = source.usable as unknown as InstrumentedPromise; + let byKey = derivedUsableCache.get(sourceUsable); + if (!byKey) { + byKey = new Map(); + derivedUsableCache.set(sourceUsable, byKey); + } + let derived = byKey.get(key) as InstrumentedPromise | undefined; + if (!derived) { + if (sourceUsable.status === 'fulfilled') { + derived = createFulfilledUsable(projectCached(sourceUsable.value as S, project, key)); + } else { + derived = instrumentPromise( + sourceUsable.then((val) => projectCached(val, project, key)) + ); + } + byKey.set(key, derived); + } + return derived; + } + ); +} + +export interface FirebaseStreamConfig { + ref: Ref; + isEqual?: (a: Ref, b: Ref) => boolean; + peekInFlight: () => InstrumentedPromise | undefined; + getOrStartInFlight: () => InstrumentedPromise; + clearInFlight: (promise?: InstrumentedPromise) => void; + subscribe: ( + onNext: (value: T, isComplete?: boolean) => void, + onError: (error: unknown) => void, + inFlightPromise: InstrumentedPromise | undefined + ) => () => void; + options?: ReactFireOptions; + getSyncInitialValue?: () => { hasValue: boolean; value: T; isComplete?: boolean }; + defaultIsComplete?: boolean; +} + +export function useFirebaseStream(config: FirebaseStreamConfig): ObservableStatus { + registerVersion('react', React.version || 'unknown'); + registerVersion('reactfire', version); + + const { + ref, + isEqual = Object.is, + peekInFlight, + getOrStartInFlight, + clearInFlight, + subscribe, + options, + getSyncInitialValue, + defaultIsComplete = false, + } = config; + + const stableRef = React.useRef(ref); + if (!isEqual(stableRef.current, ref)) { + stableRef.current = ref; + } + const canonicalRef = stableRef.current; + + const suspenseEnabled = useSuspenseEnabledFromConfigAndContext(options?.suspense); + const hasInitialData = hasInitialDataOption(options); + const initialData = getInitialDataOption(options); + const syncInit = getSyncInitialValue ? getSyncInitialValue() : undefined; + + const reqRef = React.useRef<{ ref: Ref; promise: InstrumentedPromise } | null>(null); + if (!hasInitialData) { + const existingForRef = + reqRef.current && isEqual(reqRef.current.ref, ref) ? reqRef.current.promise : peekInFlight(); + if (suspenseEnabled && (!syncInit?.hasValue || existingForRef)) { + reqRef.current = { ref, promise: existingForRef ?? getOrStartInFlight() }; + } else if (!syncInit?.hasValue && existingForRef) { + reqRef.current = { ref, promise: existingForRef }; + } else { + reqRef.current = null; + } + } else { + reqRef.current = null; + } + + const reqPromise = reqRef.current && isEqual(reqRef.current.ref, ref) ? reqRef.current.promise : undefined; + + const preloadedValue = + suspenseEnabled && !hasInitialData && reqPromise + ? unwrapOrSuspend(reqPromise) + : reqPromise?.status === 'fulfilled' + ? (reqPromise.value as T) + : undefined; + const preloadedReady = + (suspenseEnabled && !hasInitialData && Boolean(reqPromise)) || + reqPromise?.status === 'fulfilled'; + + const [state, setState] = React.useState<{ + ref: Ref; + hasValue: boolean; + value: T | undefined; + isComplete: boolean; + }>(() => { + if (syncInit?.hasValue) { + return { + ref, + hasValue: true, + value: syncInit.value, + isComplete: syncInit.isComplete ?? defaultIsComplete, + }; + } + if (preloadedReady) { + return { + ref, + hasValue: true, + value: preloadedValue, + isComplete: defaultIsComplete, + }; + } + return { + ref, + hasValue: false, + value: undefined, + isComplete: false, + }; + }); + + const firstValueDeferredRef = React.useRef<{ + ref: Ref; + promise: Promise; + resolve: () => void; + } | null>(null); + if (!firstValueDeferredRef.current || !isEqual(firstValueDeferredRef.current.ref, ref)) { + let resolve!: () => void; + const promise = new Promise((r) => { + resolve = r; + }); + if (syncInit?.hasValue || preloadedReady || hasInitialData) { + resolve(); + } + firstValueDeferredRef.current = { ref, promise, resolve }; + } + + React.useEffect(() => { + let active = true; + const resolveFirst = firstValueDeferredRef.current?.resolve; + const currentPromise = reqRef.current && isEqual(reqRef.current.ref, ref) ? reqRef.current.promise : undefined; + + if (currentPromise && currentPromise.status === 'fulfilled') { + resolveFirst?.(); + setState((prev) => + isEqual(prev.ref, ref) && prev.hasValue + ? prev + : { ref, hasValue: true, value: currentPromise.value as T, isComplete: defaultIsComplete } + ); + } + + setTimeout(() => { + clearInFlight(currentPromise); + }, 0); + + const unsubscribe = subscribe( + (nextValue, isComplete = defaultIsComplete) => { + if (active) { + resolveFirst?.(); + setState((prev) => + isEqual(prev.ref, ref) && + prev.hasValue && + Object.is(prev.value, nextValue) && + prev.isComplete === isComplete + ? prev + : { + ref, + hasValue: true, + value: nextValue, + isComplete, + } + ); + } + }, + (error: unknown) => { + if (active) { + resolveFirst?.(); + setState(() => { + throw error; + }); + } + }, + currentPromise + ); + + return () => { + active = false; + unsubscribe(); + }; + }, [canonicalRef]); + + const isCurrent = isEqual(state.ref, ref); + const hasStreamValue = (isCurrent && state.hasValue) || Boolean(syncInit?.hasValue) || preloadedReady; + const streamValue = ( + isCurrent && state.hasValue + ? state.value + : syncInit?.hasValue + ? syncInit.value + : preloadedValue + ) as T; + const hasResolved = hasStreamValue || hasInitialData; + const currentValue = (hasStreamValue ? streamValue : initialData) as T; + const currentIsComplete = + isCurrent && state.hasValue + ? state.isComplete + : syncInit?.hasValue + ? (syncInit.isComplete ?? defaultIsComplete) + : hasStreamValue + ? defaultIsComplete + : false; + + const usableRef = React.useRef<{ ref: Ref; promise: InstrumentedPromise } | null>(null); + if (preloadedReady && reqPromise && (!usableRef.current || !isEqual(usableRef.current.ref, ref))) { + usableRef.current = { ref, promise: reqPromise }; + } + + const getUsable = (): ReactFireUsable => { + if (hasResolved) { + if ( + !usableRef.current || + !isEqual(usableRef.current.ref, ref) || + usableRef.current.promise.status !== 'fulfilled' || + !Object.is(usableRef.current.promise.value, currentValue) + ) { + usableRef.current = { + ref, + promise: createFulfilledUsable(currentValue), + }; + } + return usableRef.current.promise; + } + + if (!reqRef.current || !isEqual(reqRef.current.ref, ref)) { + reqRef.current = { ref, promise: getOrStartInFlight() }; + } + usableRef.current = reqRef.current; + return reqRef.current.promise; + }; + + if (hasStreamValue) { + return attachUsable( + { + status: 'success', + hasEmitted: true, + isComplete: currentIsComplete, + data: streamValue, + error: undefined, + firstValuePromise: Promise.resolve(), + }, + getUsable + ); + } + + return attachUsable( + { + status: hasInitialData ? 'success' : 'loading', + hasEmitted: hasInitialData, + isComplete: false, + data: initialData as T, + error: undefined, + firstValuePromise: firstValueDeferredRef.current.promise, + }, + getUsable + ); +} diff --git a/src/useObservable.ts b/src/useObservable.ts index 237f6e35..754aee69 100644 --- a/src/useObservable.ts +++ b/src/useObservable.ts @@ -1,23 +1,34 @@ import * as React from 'react'; +import { registerVersion } from 'firebase/app'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { Observable } from 'rxjs'; import { SuspenseSubject } from './SuspenseSubject.js'; -import { useSuspenseEnabledFromConfigAndContext } from './firebaseApp.js'; -import { ReactFireGlobals, ReactFireOptions } from './index.js'; +import { useSuspenseEnabledFromConfigAndContext, version } from './firebaseApp.js'; +import { attachUsable, createFulfilledUsable } from './useFirebaseStream.js'; +import type { ObservableStatus, ReactFireOptions, ReactFireUsable } from './useFirebaseStream.js'; +import type { ReactFireGlobals } from './index.js'; -const DEFAULT_TIMEOUT = 30_000; +export type { ObservableStatus, ReactFireUsable }; -// Since we're side-effect free, we need to ensure our observable cache is global -const preloadedObservables: Map> = (globalThis as any as ReactFireGlobals)._reactFirePreloadedObservables || new Map(); +const DEFAULT_TIMEOUT = 30_000; -if (!(globalThis as any as ReactFireGlobals)._reactFirePreloadedObservables) { - (globalThis as any as ReactFireGlobals)._reactFirePreloadedObservables = preloadedObservables; +function getPreloadedObservables(): Map> { + const globals = globalThis as unknown as ReactFireGlobals; + if (!globals._reactFirePreloadedObservables) { + globals._reactFirePreloadedObservables = new Map(); + } + return globals._reactFirePreloadedObservables; } -// Starts listening to an Observable. -// Call this once you know you're going to render a -// child that will consume the observable +/** + * Starts listening to an Observable. + * Call this once you know you're going to render a + * child that will consume the observable. + * + * @deprecated RxJS integration (`preloadObservable`, `useObservable`, `SuspenseSubject`) is deprecated and will be removed in v5.0.0. + */ export function preloadObservable(source: Observable, id: string, suspenseEnabled = false) { + const preloadedObservables = getPreloadedObservables(); if (preloadedObservables.has(id)) { return preloadedObservables.get(id) as SuspenseSubject; } else { @@ -27,48 +38,19 @@ export function preloadObservable(source: Observable, id: string, suspense } } -export interface ObservableStatus { - /** - * The loading status. - * - * - `loading`: Waiting for the first value from an observable - * - `error`: Something went wrong. Check `ObservableStatus.error` for more details - * - `success`: The hook has emitted at least one value - * - * If `initialData` is passed in, this will skip `loading` and go straight to `success`. - */ - status: 'loading' | 'error' | 'success'; - /** - * Indicates whether the hook has emitted a value at some point - * - * If `initialData` is passed in, this will be `true`. - */ - hasEmitted: boolean; // has received at least one value - /** - * If this is `true`, the hook will be emitting no further items. - */ - isComplete: boolean; - /** - * The most recent value. - * - * If `initialData` is passed in, the first value of `data` will be the valuea provided in `initialData` **UNLESS** the underlying observable is ready, in which case it will skip `initialData`. - */ - data: T; - /** - * Any error that may have occurred in the underlying observable - */ - error: Error | undefined; - /** - * Promise that resolves after first emit from observable - */ - firstValuePromise: Promise; -} - +/** + * Subscribe to an RxJS `Observable`. + * + * @deprecated RxJS integration (`useObservable`, `preloadObservable`, `SuspenseSubject`) is deprecated and will be removed in v5.0.0. + */ export function useObservable(observableId: string, source: Observable, config: ReactFireOptions = {}): ObservableStatus { if (!observableId) { throw new Error('cannot call useObservable without an observableId'); } + registerVersion('react', React.version || 'unknown'); + registerVersion('reactfire', version); + const suspenseEnabled = useSuspenseEnabledFromConfigAndContext(config.suspense); // Register the observable with the cache @@ -81,7 +63,8 @@ export function useObservable(observableId: string, source: Observa throw observable.firstEmission; } - const subscribe = React.useCallback((onStoreChange: () => void) => { + const subscribe = React.useCallback( + (onStoreChange: () => void) => { const subscription = observable.subscribe({ next: () => { onStoreChange(); @@ -97,8 +80,10 @@ export function useObservable(observableId: string, source: Observa return () => { subscription.unsubscribe(); - } - }, [observable]) + }; + }, + [observable] + ); const getSnapshot = React.useCallback<() => ObservableStatus>(() => { return observable.immutableStatus; @@ -115,14 +100,17 @@ export function useObservable(observableId: string, source: Observa if (serverSnapshotRef.current === undefined) { const initialDataValue = config?.initialData ?? config?.startWithValue; - serverSnapshotRef.current = { - status: hasInitialData ? 'success' : 'loading', - hasEmitted: hasInitialData, - isComplete: false, - data: initialDataValue, - error: undefined, - firstValuePromise: observable.firstEmission - }; + serverSnapshotRef.current = attachUsable( + { + status: hasInitialData ? 'success' : 'loading', + hasEmitted: hasInitialData, + isComplete: false, + data: initialDataValue, + error: undefined, + firstValuePromise: observable.firstEmission, + }, + () => createFulfilledUsable(initialDataValue) + ); } return serverSnapshotRef.current; @@ -145,12 +133,15 @@ export function useObservable(observableId: string, source: Observa throw update.error; } - return { - ...update, - data: initialDataValue, - status: 'success', - hasEmitted: true, - } as ObservableStatus; + return attachUsable( + { + ...update, + data: initialDataValue, + status: 'success', + hasEmitted: true, + }, + () => createFulfilledUsable(initialDataValue) + ); } // throw an error if there is an error diff --git a/test/auth.test.tsx b/test/auth.test.tsx index 647ef683..0504604e 100644 --- a/test/auth.test.tsx +++ b/test/auth.test.tsx @@ -407,4 +407,95 @@ describe('Authentication', () => { expect(getIdTokenResult).toHaveBeenCalledTimes(1); }); }); + + describe('React 19 .usable / .experimental_usable with React.use()', () => { + const use = + (React as unknown as { use?: (usable: Promise) => T }).use ?? + function usePolyfill(usable: Promise & { status?: string; value?: T; reason?: unknown }): T { + if (usable.status === 'fulfilled') return usable.value as T; + if (usable.status === 'rejected') throw usable.reason; + throw usable; + }; + + it('unwraps user with use(useUser().usable) and updates live on signIn and signOut', async () => { + const UserBadge = () => { + const user = use(useUser().experimental_usable); + return
{user ? 'logged-in' : 'logged-out'}
; + }; + + const { getByTestId } = render( + + Loading...}> + + + + ); + + await waitFor(() => expect(getByTestId('usable-user')).toHaveTextContent('logged-out')); + + await act(async () => { + await signIn(); + }); + + await waitFor(() => expect(getByTestId('usable-user')).toHaveTextContent('logged-in')); + + await act(async () => { + await getAuth(app).signOut(); + }); + + await waitFor(() => expect(getByTestId('usable-user')).toHaveTextContent('logged-out')); + }); + + it('unwraps sign-in status with use(useSigninCheck().usable) without uncached promise warnings', async () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + const SigninBadge = () => { + const check = use(useSigninCheck().usable); + return
{check.signedIn ? 'in' : 'out'}
; + }; + + const { getByTestId } = render( + + Loading...}> + + + + ); + + await waitFor(() => expect(getByTestId('usable-signin')).toHaveTextContent('out')); + + await act(async () => { + await signIn(); + }); + + await waitFor(() => expect(getByTestId('usable-signin')).toHaveTextContent('in')); + + const uncachedWarnings = errorSpy.mock.calls.filter((args) => + String(args[0] ?? '').includes('uncached promise') + ); + expect(uncachedWarnings).toHaveLength(0); + errorSpy.mockRestore(); + }); + + it('supports passing auth instance directly to useUser(auth) and useIdTokenResult(auth)', async () => { + const authInstance = getAuth(app); + await authInstance.signOut(); + + const { result: userResult } = renderHook(() => useUser(authInstance)); + const { result: tokenResult } = renderHook(() => useIdTokenResult(authInstance)); + + await waitFor(() => expect(userResult.current.status).toEqual('success')); + await waitFor(() => expect(tokenResult.current.status).toEqual('success')); + expect(userResult.current.data).toBeNull(); + expect(tokenResult.current.data).toBeNull(); + + await act(async () => { + await signIn(); + }); + + await waitFor(() => expect(userResult.current.data).not.toBeNull()); + await waitFor(() => expect(tokenResult.current.data).not.toBeNull()); + expect(tokenResult.current.data?.token).toBeDefined(); + }); + }); }); diff --git a/test/database.test.tsx b/test/database.test.tsx index b3d912a1..b8d99813 100644 --- a/test/database.test.tsx +++ b/test/database.test.tsx @@ -1,10 +1,9 @@ import { renderHook, act, cleanup, waitFor } from '@testing-library/react'; import * as React from 'react'; -import { useDatabaseObject, useDatabaseList, useDatabaseObjectData, useDatabaseListData, FirebaseAppProvider, DatabaseProvider } from '../src/index'; +import { useDatabaseObject, useDatabaseList, useDatabaseObjectData, useDatabaseListData, FirebaseAppProvider, DatabaseProvider, QueryChange } from '../src/index'; import { baseConfig } from './appConfig'; import { initializeApp } from 'firebase/app'; import { getDatabase, connectDatabaseEmulator, ref, set, push, query, orderByChild, equalTo, get } from 'firebase/database'; -import { QueryChange } from 'rxfire/database'; import { randomString } from './test-utils'; describe('Realtime Database (RTDB)', () => { diff --git a/test/firebaseApp.test.tsx b/test/firebaseApp.test.tsx index b46dcd0d..5dcd8ea1 100644 --- a/test/firebaseApp.test.tsx +++ b/test/firebaseApp.test.tsx @@ -1,12 +1,23 @@ import { cleanup, render, waitFor, renderHook } from '@testing-library/react'; +import * as firebaseAppModule from 'firebase/app'; import { initializeApp, deleteApp, getApps } from 'firebase/app'; import '@testing-library/jest-dom/extend-expect'; import * as React from 'react'; -import { useFirebaseApp, FirebaseAppProvider, version } from '../src/index'; +import { Subject } from 'rxjs'; +import { useFirebaseApp, FirebaseAppProvider, useObservable, version } from '../src/index'; import pkg from '../package.json'; +vi.mock('firebase/app', async () => { + const actual = await vi.importActual('firebase/app'); + return { + ...actual, + registerVersion: vi.fn(actual.registerVersion), + }; +}); + afterEach(() => { cleanup(); + vi.clearAllMocks(); getApps().forEach(deleteApp); }); @@ -29,6 +40,26 @@ describe('FirebaseAppProvider', () => { render(); expect(getApps()).toHaveLength(1); + expect(firebaseAppModule.registerVersion).toHaveBeenCalledWith('react', React.version || 'unknown'); + expect(firebaseAppModule.registerVersion).toHaveBeenCalledWith('reactfire', version); + }); + + it('Registers react and reactfire versions even if the app was already initialized', () => { + initializeApp(DEFAULT_APP_CONFIG); + vi.clearAllMocks(); + + render(); + + expect(firebaseAppModule.registerVersion).toHaveBeenCalledWith('react', React.version || 'unknown'); + expect(firebaseAppModule.registerVersion).toHaveBeenCalledWith('reactfire', version); + }); + + it('Registers react and reactfire versions when useObservable is called', () => { + const subject = new Subject(); + renderHook(() => useObservable('register-version-test', subject, { initialData: 'hello' })); + + expect(firebaseAppModule.registerVersion).toHaveBeenCalledWith('react', React.version || 'unknown'); + expect(firebaseAppModule.registerVersion).toHaveBeenCalledWith('reactfire', version); }); }); diff --git a/test/firestore.test.tsx b/test/firestore.test.tsx index 0dcf0c23..6558f872 100644 --- a/test/firestore.test.tsx +++ b/test/firestore.test.tsx @@ -1,4 +1,4 @@ -import { renderHook, act, cleanup, waitFor } from '@testing-library/react'; +import { render, renderHook, act, cleanup, waitFor } from '@testing-library/react'; import * as React from 'react'; import '@testing-library/jest-dom/extend-expect'; @@ -353,4 +353,174 @@ describe('Firestore', () => { expect(result.current.data).toEqual([mockData]); }); }); + + describe('React 19 .usable / .experimental_usable with React.use()', () => { + const use = + (React as unknown as { use?: (usable: Promise) => T }).use ?? + function usePolyfill(usable: Promise & { status?: string; value?: T; reason?: unknown }): T { + if (usable.status === 'fulfilled') return usable.value as T; + if (usable.status === 'rejected') throw usable.reason; + throw usable; + }; + + it('suspends on initial load with use(useFirestoreDoc(ref).usable) and updates live without re-suspending', async () => { + const ref = doc(collection(db, randomString()), randomString()); + await setDoc(ref, { a: 'initial' }); + + const errorSpy = vi.spyOn(console, 'error'); + let fallbackMounts = 0; + + const Fallback = () => { + React.useEffect(() => { + fallbackMounts++; + }, []); + return
Loading...
; + }; + + const DocView = () => { + const snap = use(useFirestoreDoc(ref).experimental_usable); + return
{snap.data()?.a}
; + }; + + let rendered!: ReturnType; + await act(async () => { + rendered = render( + + }> + + + + ); + }); + const { getByTestId, queryByTestId } = rendered; + + await waitFor(() => expect(getByTestId('doc-value')).toHaveTextContent('initial')); + expect(queryByTestId('fallback')).toBeNull(); + const initialFallbackMounts = fallbackMounts; + + await act(() => setDoc(ref, { a: 'updated-live' })); + + await waitFor(() => expect(getByTestId('doc-value')).toHaveTextContent('updated-live')); + expect(fallbackMounts).toEqual(initialFallbackMounts); + + const uncachedWarnings = errorSpy.mock.calls.filter((args) => + String(args[0] ?? '').includes('uncached promise') + ); + expect(uncachedWarnings).toHaveLength(0); + errorSpy.mockRestore(); + }); + + it('unwraps data with use(useFirestoreDocData(ref).usable) and updates live', async () => { + const ref = doc(collection(db, randomString()), 'my-doc-id') as DocumentReference<{ title: string; id?: string }>; + await setDoc(ref, { title: 'first' }); + + const DocDataView = () => { + const data = use(useFirestoreDocData(ref, { idField: 'id' }).usable); + return
{`${data?.id}:${data?.title}`}
; + }; + + let rendered!: ReturnType; + await act(async () => { + rendered = render( + + Loading...}> + + + + ); + }); + const { getByTestId } = rendered; + + await waitFor(() => expect(getByTestId('doc-data')).toHaveTextContent('my-doc-id:first')); + + await act(() => setDoc(ref, { title: 'second' })); + + await waitFor(() => expect(getByTestId('doc-data')).toHaveTextContent('my-doc-id:second')); + }); + + it('suspends and updates live with use(useFirestoreCollectionData(colRef).usable)', async () => { + const colRef = collection(db, randomString()); + await addDoc(colRef, { name: 'one' }); + + const ColView = () => { + const items = use(useFirestoreCollectionData(colRef).usable) as Array<{ name: string }>; + return
{items.map((i) => i.name).join(',')}
; + }; + + let rendered!: ReturnType; + await act(async () => { + rendered = render( + + Loading...}> + + + + ); + }); + const { getByTestId } = rendered; + + await waitFor(() => expect(getByTestId('col-items')).toHaveTextContent('one')); + + await act(() => addDoc(colRef, { name: 'two' })); + + await waitFor(() => expect(getByTestId('col-items')).toHaveTextContent(/one.*two|two.*one/)); + }); + + it('preserves === array identity across unrelated re-renders in useFirestoreCollectionData', async () => { + const colRef = collection(db, randomString()); + await addDoc(colRef, { name: 'stable' }); + + const seenArrays: Array = []; + let triggerRerender!: () => void; + + const ColStabilityView = () => { + const [, setTick] = React.useState(0); + triggerRerender = () => setTick((t) => t + 1); + const { status, data } = useFirestoreCollectionData(colRef); + if (status === 'success') { + seenArrays.push(data); + } + return
{status === 'success' ? data.length : 'loading'}
; + }; + + const { getByTestId } = render( + + + + ); + + await waitFor(() => expect(getByTestId('col-stable')).toHaveTextContent('1')); + expect(seenArrays.length).toBeGreaterThanOrEqual(1); + const firstArray = seenArrays[seenArrays.length - 1]; + + await act(async () => { + triggerRerender(); + }); + + const secondArray = seenArrays[seenArrays.length - 1]; + expect(secondArray).toBe(firstArray); + }); + + it('supports { once: true } option on useFirestoreDoc and useFirestoreCollection', async () => { + const colRef = collection(db, randomString()); + const docRef = doc(colRef, randomString()); + await setDoc(docRef, { a: 'first' }); + + const { result: docResult } = renderHook(() => useFirestoreDoc(docRef, { once: true }), { wrapper: Provider }); + const { result: colResult } = renderHook(() => useFirestoreCollection(colRef, { once: true }), { wrapper: Provider }); + + await waitFor(() => expect(docResult.current.status).toEqual('success')); + await waitFor(() => expect(colResult.current.status).toEqual('success')); + expect(docResult.current.isComplete).toEqual(true); + expect(colResult.current.isComplete).toEqual(true); + expect(docResult.current.data.data()).toEqual({ a: 'first' }); + expect(colResult.current.data.docs).toHaveLength(1); + + await act(() => setDoc(docRef, { a: 'second' })); + await act(() => addDoc(colRef, { a: 'third' })); + + expect(docResult.current.data.data()).toEqual({ a: 'first' }); + expect(colResult.current.data.docs).toHaveLength(1); + }); + }); }); diff --git a/test/performance.test.tsx b/test/performance.test.tsx index 25d0fb4a..b3922dc3 100644 --- a/test/performance.test.tsx +++ b/test/performance.test.tsx @@ -13,6 +13,14 @@ const createTrace = vi.fn(() => ({ stop: traceEnd, })); +vi.mock('firebase/performance', async () => { + const actual = await vi.importActual('firebase/performance'); + return { + ...actual, + trace: () => createTrace(), + }; +}); + const mockPerf = vi.fn(() => { return { trace: createTrace }; }) as any as () => FirebasePerformance; @@ -146,4 +154,39 @@ describe('SuspenseWithPerf', () => { expect(measure).toHaveBeenCalledTimes(1); expect(measure).toHaveBeenCalledWith('test lifecycle', '_test lifecycleStart[0]', '_test lifecycleEnd[0]'); }); + + it('records a Firebase Performance trace when perf prop is provided', async () => { + const o$ = new Subject(); + let shouldThrow = true; + + const promise = new Promise((resolve) => { + o$.subscribe(() => { + shouldThrow = false; + resolve(true); + }); + }); + + const Comp = () => { + if (shouldThrow) { + throw promise; + } + return

Actual

; + }; + + const perfInstance = { app: mockFirebase } as unknown as FirebasePerformance; + + const { getByTestId } = render( + Fallback} traceId="fb-perf-trace"> + + + ); + + expect(getByTestId('fallback')).toBeInTheDocument(); + expect(traceStart).toHaveBeenCalledTimes(1); + expect(traceEnd).toHaveBeenCalledTimes(0); + + act(() => o$.next('a')); + await waitFor(() => getByTestId('child')); + expect(traceEnd).toHaveBeenCalledTimes(1); + }); }); diff --git a/vite.config.ts b/vite.config.ts index 30d83cb9..90543452 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -18,6 +18,8 @@ const externals = [ // loads client-side in a Next.js App Router (strict ESM) context. Externalizing keeps // the shim's React 16/17 fallback intact while removing the require from our dist. 'use-sync-external-store/shim', + 'rxjs', + 'rxjs/operators', 'firebase', 'firebase/app', 'firebase/firestore', From a1c221f979e873304f79395afb218c5b118864c7 Mon Sep 17 00:00:00 2001 From: Jeff Huleatt <3759507+jhuleatt@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:17:47 +0000 Subject: [PATCH 2/2] feat: use createInFlightCache + listenUntilReleased and preserveModules ESM output --- docs/reference/classes/ReactFireError.md | 10 +- docs/reference/enumerations/ListenEvent.md | 12 +- docs/reference/functions/AuthCheck.md | 2 +- docs/reference/functions/ClaimsCheck.md | 2 +- docs/reference/functions/StorageImage.md | 2 +- docs/reference/functions/checkIdField.md | 2 +- docs/reference/functions/checkOptions.md | 2 +- docs/reference/functions/checkinitialData.md | 2 +- .../functions/preloadFirestoreDoc.md | 6 +- docs/reference/functions/preloadUser.md | 2 +- docs/reference/functions/useAnalytics.md | 2 +- docs/reference/functions/useAppCheck.md | 2 +- docs/reference/functions/useAuth.md | 2 +- .../functions/useCallableFunctionResponse.md | 2 +- docs/reference/functions/useDatabase.md | 2 +- docs/reference/functions/useDatabaseList.md | 2 +- .../functions/useDatabaseListData.md | 2 +- docs/reference/functions/useDatabaseObject.md | 2 +- .../functions/useDatabaseObjectData.md | 2 +- docs/reference/functions/useFirestore.md | 2 +- .../functions/useFirestoreCollection.md | 2 +- .../functions/useFirestoreCollectionData.md | 2 +- docs/reference/functions/useFirestoreDoc.md | 2 +- .../functions/useFirestoreDocData.md | 2 +- .../functions/useFirestoreDocDataOnce.md | 2 +- .../functions/useFirestoreDocOnce.md | 2 +- docs/reference/functions/useFunctions.md | 2 +- docs/reference/functions/useIdTokenResult.md | 4 +- docs/reference/functions/usePerformance.md | 2 +- docs/reference/functions/useRemoteConfig.md | 2 +- .../reference/functions/useRemoteConfigAll.md | 2 +- .../functions/useRemoteConfigBoolean.md | 2 +- .../functions/useRemoteConfigNumber.md | 2 +- .../functions/useRemoteConfigString.md | 2 +- .../functions/useRemoteConfigValue.md | 2 +- docs/reference/functions/useSigninCheck.md | 2 +- docs/reference/functions/useStorage.md | 2 +- .../functions/useStorageDownloadURL.md | 4 +- docs/reference/functions/useStorageTask.md | 2 +- docs/reference/functions/useUser.md | 4 +- docs/reference/interfaces/AuthCheckProps.md | 10 +- docs/reference/interfaces/ClaimCheckErrors.md | 2 +- docs/reference/interfaces/ClaimsCheckProps.md | 10 +- docs/reference/interfaces/ClaimsValidator.md | 4 +- docs/reference/interfaces/ObservableStatus.md | 18 +- docs/reference/interfaces/QueryChange.md | 8 +- docs/reference/interfaces/ReactFireOptions.md | 14 +- .../interfaces/SignInCheckOptionsBasic.md | 16 +- .../SignInCheckOptionsClaimsObject.md | 18 +- .../SignInCheckOptionsClaimsValidator.md | 18 +- .../reference/type-aliases/ReactFireUsable.md | 2 +- .../type-aliases/SigninCheckResult.md | 2 +- .../type-aliases/StorageImageProps.md | 10 +- docs/reference/variables/AnalyticsProvider.md | 2 +- docs/reference/variables/AppCheckProvider.md | 2 +- docs/reference/variables/AuthProvider.md | 2 +- docs/reference/variables/DatabaseProvider.md | 2 +- docs/reference/variables/FirestoreProvider.md | 2 +- docs/reference/variables/FunctionsProvider.md | 2 +- .../variables/PerformanceProvider.md | 2 +- .../variables/RemoteConfigProvider.md | 2 +- docs/reference/variables/StorageProvider.md | 2 +- docs/reference/variables/useInitAnalytics.md | 2 +- docs/reference/variables/useInitAppCheck.md | 2 +- docs/reference/variables/useInitAuth.md | 2 +- docs/reference/variables/useInitDatabase.md | 2 +- docs/reference/variables/useInitFirestore.md | 2 +- docs/reference/variables/useInitFunctions.md | 2 +- .../reference/variables/useInitPerformance.md | 2 +- .../variables/useInitRemoteConfig.md | 2 +- docs/reference/variables/useInitStorage.md | 2 +- package-lock.json | 2 +- package.json | 2 +- src/SuspenseSubject.ts | 7 +- src/auth.tsx | 175 ++++++++------- src/database.tsx | 106 ++------- src/firestore.tsx | 176 ++++++--------- src/functions.tsx | 38 +--- src/remote-config.tsx | 46 ++-- src/sdk.tsx | 23 +- src/storage.tsx | 105 ++++----- src/useFirebaseStream.ts | 204 ++++++++++++++++-- test/firestore.test.tsx | 35 ++- test/useObservable.test.tsx | 30 +++ tsconfig.json | 2 +- vite.config.ts | 9 +- 86 files changed, 637 insertions(+), 599 deletions(-) diff --git a/docs/reference/classes/ReactFireError.md b/docs/reference/classes/ReactFireError.md index 89abe675..d7c84298 100644 --- a/docs/reference/classes/ReactFireError.md +++ b/docs/reference/classes/ReactFireError.md @@ -6,7 +6,7 @@ # ~~Class: ReactFireError~~ -Defined in: [src/useFirebaseStream.ts:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L20) +Defined in: [src/useFirebaseStream.ts:35](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L35) ## Deprecated @@ -22,7 +22,7 @@ Error class used only by the deprecated `useSigninCheck` hook. Will be removed i > **new ReactFireError**(`code`, `message`, `customData?`): `ReactFireError` -Defined in: [src/useFirebaseStream.ts:23](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L23) +Defined in: [src/useFirebaseStream.ts:38](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L38) #### Parameters @@ -64,7 +64,7 @@ Defined in: node\_modules/typescript/lib/lib.es2022.error.d.ts:24 > `readonly` **code**: `string` -Defined in: [src/useFirebaseStream.ts:23](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L23) +Defined in: [src/useFirebaseStream.ts:38](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L38) *** @@ -72,7 +72,7 @@ Defined in: [src/useFirebaseStream.ts:23](https://github.com/FirebaseExtended/re > `optional` **customData?**: `Record`\<`string`, `unknown`\> -Defined in: [src/useFirebaseStream.ts:23](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L23) +Defined in: [src/useFirebaseStream.ts:38](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L38) *** @@ -92,7 +92,7 @@ Defined in: node\_modules/typescript/lib/lib.es5.d.ts:1075 > `readonly` **name**: `"ReactFireError"` = `'ReactFireError'` -Defined in: [src/useFirebaseStream.ts:21](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L21) +Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) #### Overrides diff --git a/docs/reference/enumerations/ListenEvent.md b/docs/reference/enumerations/ListenEvent.md index 3ac54b15..ebee45dc 100644 --- a/docs/reference/enumerations/ListenEvent.md +++ b/docs/reference/enumerations/ListenEvent.md @@ -6,7 +6,7 @@ # ~~Enumeration: ListenEvent~~ -Defined in: [src/database.tsx:16](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L16) +Defined in: [src/database.tsx:17](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L17) ## Deprecated @@ -18,7 +18,7 @@ Legacy rxfire change event enum. In v5.0.0, `useDatabaseObject` returns `DataSna > **added**: `"child_added"` -Defined in: [src/database.tsx:17](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L17) +Defined in: [src/database.tsx:18](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L18) *** @@ -26,7 +26,7 @@ Defined in: [src/database.tsx:17](https://github.com/FirebaseExtended/reactfire/ > **changed**: `"child_changed"` -Defined in: [src/database.tsx:19](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L19) +Defined in: [src/database.tsx:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L20) *** @@ -34,7 +34,7 @@ Defined in: [src/database.tsx:19](https://github.com/FirebaseExtended/reactfire/ > **moved**: `"child_moved"` -Defined in: [src/database.tsx:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L20) +Defined in: [src/database.tsx:21](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L21) *** @@ -42,7 +42,7 @@ Defined in: [src/database.tsx:20](https://github.com/FirebaseExtended/reactfire/ > **removed**: `"child_removed"` -Defined in: [src/database.tsx:18](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L18) +Defined in: [src/database.tsx:19](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L19) *** @@ -50,4 +50,4 @@ Defined in: [src/database.tsx:18](https://github.com/FirebaseExtended/reactfire/ > **value**: `"value"` -Defined in: [src/database.tsx:21](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L21) +Defined in: [src/database.tsx:22](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L22) diff --git a/docs/reference/functions/AuthCheck.md b/docs/reference/functions/AuthCheck.md index 6f0b573e..23e4c908 100644 --- a/docs/reference/functions/AuthCheck.md +++ b/docs/reference/functions/AuthCheck.md @@ -8,7 +8,7 @@ > **AuthCheck**(`__namedParameters`): `ReactElement` -Defined in: [src/auth.tsx:638](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L638) +Defined in: [src/auth.tsx:633](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L633) Conditionally render children based on signed-in status and [custom claims](https://firebase.google.com/docs/auth/admin/custom-claims). diff --git a/docs/reference/functions/ClaimsCheck.md b/docs/reference/functions/ClaimsCheck.md index 07cbc7ca..2f13898d 100644 --- a/docs/reference/functions/ClaimsCheck.md +++ b/docs/reference/functions/ClaimsCheck.md @@ -8,7 +8,7 @@ > **ClaimsCheck**(`__namedParameters`): `Element` -Defined in: [src/auth.tsx:598](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L598) +Defined in: [src/auth.tsx:593](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L593) ## Parameters diff --git a/docs/reference/functions/StorageImage.md b/docs/reference/functions/StorageImage.md index aa564797..de582e6b 100644 --- a/docs/reference/functions/StorageImage.md +++ b/docs/reference/functions/StorageImage.md @@ -8,7 +8,7 @@ > **StorageImage**(`props`): `Element` -Defined in: [src/storage.tsx:214](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L214) +Defined in: [src/storage.tsx:185](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L185) ## Parameters diff --git a/docs/reference/functions/checkIdField.md b/docs/reference/functions/checkIdField.md index 2a1f0332..b4ec33c8 100644 --- a/docs/reference/functions/checkIdField.md +++ b/docs/reference/functions/checkIdField.md @@ -8,7 +8,7 @@ > **checkIdField**(`options`): `any` -Defined in: [src/useFirebaseStream.ts:78](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L78) +Defined in: [src/useFirebaseStream.ts:93](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L93) ## Parameters diff --git a/docs/reference/functions/checkOptions.md b/docs/reference/functions/checkOptions.md index e16f3c43..132dc669 100644 --- a/docs/reference/functions/checkOptions.md +++ b/docs/reference/functions/checkOptions.md @@ -8,7 +8,7 @@ > **checkOptions**(`options`, `field`): `any` -Defined in: [src/useFirebaseStream.ts:59](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L59) +Defined in: [src/useFirebaseStream.ts:74](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L74) ## Parameters diff --git a/docs/reference/functions/checkinitialData.md b/docs/reference/functions/checkinitialData.md index 6bc255a3..2c8e865c 100644 --- a/docs/reference/functions/checkinitialData.md +++ b/docs/reference/functions/checkinitialData.md @@ -8,7 +8,7 @@ > **checkinitialData**(`options`): `any` -Defined in: [src/useFirebaseStream.ts:71](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L71) +Defined in: [src/useFirebaseStream.ts:86](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L86) ## Parameters diff --git a/docs/reference/functions/preloadFirestoreDoc.md b/docs/reference/functions/preloadFirestoreDoc.md index 87feee25..bd0a08e9 100644 --- a/docs/reference/functions/preloadFirestoreDoc.md +++ b/docs/reference/functions/preloadFirestoreDoc.md @@ -6,9 +6,9 @@ # ~~Function: preloadFirestoreDoc()~~ -> **preloadFirestoreDoc**(`refProvider`): `Promise`\<`DocumentSnapshot`\<`DocumentData`, `DocumentData`\>\> +> **preloadFirestoreDoc**(`refProvider`): `Promise`\<`DocumentSnapshot`\<`any`, `DocumentData`\>\> -Defined in: [src/firestore.tsx:108](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L108) +Defined in: [src/firestore.tsx:95](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L95) Preload a subscription to a Firestore document reference. @@ -22,7 +22,7 @@ Use this to warm up `useFirestoreDoc` for a specific document ## Returns -`Promise`\<`DocumentSnapshot`\<`DocumentData`, `DocumentData`\>\> +`Promise`\<`DocumentSnapshot`\<`any`, `DocumentData`\>\> ## Deprecated diff --git a/docs/reference/functions/preloadUser.md b/docs/reference/functions/preloadUser.md index 2d2a2c68..f2eac792 100644 --- a/docs/reference/functions/preloadUser.md +++ b/docs/reference/functions/preloadUser.md @@ -8,7 +8,7 @@ > **preloadUser**(`authResolver`): `Promise`\<`User` \| `null`\> -Defined in: [src/auth.tsx:96](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L96) +Defined in: [src/auth.tsx:90](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L90) ## Parameters diff --git a/docs/reference/functions/useAnalytics.md b/docs/reference/functions/useAnalytics.md index 54acae8c..9e95c907 100644 --- a/docs/reference/functions/useAnalytics.md +++ b/docs/reference/functions/useAnalytics.md @@ -8,7 +8,7 @@ > **useAnalytics**(): `Analytics` -Defined in: [src/sdk.tsx:132](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L132) +Defined in: [src/sdk.tsx:119](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L119) ## Returns diff --git a/docs/reference/functions/useAppCheck.md b/docs/reference/functions/useAppCheck.md index 1febd346..1020966e 100644 --- a/docs/reference/functions/useAppCheck.md +++ b/docs/reference/functions/useAppCheck.md @@ -8,7 +8,7 @@ > **useAppCheck**(): `AppCheck` -Defined in: [src/sdk.tsx:128](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L128) +Defined in: [src/sdk.tsx:115](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L115) ## Returns diff --git a/docs/reference/functions/useAuth.md b/docs/reference/functions/useAuth.md index 4e3d631f..64f5a7a8 100644 --- a/docs/reference/functions/useAuth.md +++ b/docs/reference/functions/useAuth.md @@ -8,7 +8,7 @@ > **useAuth**(): `Auth` -Defined in: [src/sdk.tsx:130](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L130) +Defined in: [src/sdk.tsx:117](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L117) ## Returns diff --git a/docs/reference/functions/useCallableFunctionResponse.md b/docs/reference/functions/useCallableFunctionResponse.md index ccf4c931..5301cd4d 100644 --- a/docs/reference/functions/useCallableFunctionResponse.md +++ b/docs/reference/functions/useCallableFunctionResponse.md @@ -8,7 +8,7 @@ > **useCallableFunctionResponse**\<`RequestData`, `ResponseData`\>(`functionName`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`ResponseData`\> -Defined in: [src/functions.tsx:35](https://github.com/FirebaseExtended/reactfire/blob/main/src/functions.tsx#L35) +Defined in: [src/functions.tsx:16](https://github.com/FirebaseExtended/reactfire/blob/main/src/functions.tsx#L16) Calls a callable function. diff --git a/docs/reference/functions/useDatabase.md b/docs/reference/functions/useDatabase.md index fc81e2e5..20e1263d 100644 --- a/docs/reference/functions/useDatabase.md +++ b/docs/reference/functions/useDatabase.md @@ -8,7 +8,7 @@ > **useDatabase**(): `Database` -Defined in: [src/sdk.tsx:134](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L134) +Defined in: [src/sdk.tsx:121](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L121) ## Returns diff --git a/docs/reference/functions/useDatabaseList.md b/docs/reference/functions/useDatabaseList.md index e71c3979..9040da96 100644 --- a/docs/reference/functions/useDatabaseList.md +++ b/docs/reference/functions/useDatabaseList.md @@ -8,7 +8,7 @@ > **useDatabaseList**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`QueryChange`](../interfaces/QueryChange.md)[] \| `T`[]\> -Defined in: [src/database.tsx:202](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L202) +Defined in: [src/database.tsx:139](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L139) Subscribe to a Realtime Database list diff --git a/docs/reference/functions/useDatabaseListData.md b/docs/reference/functions/useDatabaseListData.md index 2b1c760b..9652f00e 100644 --- a/docs/reference/functions/useDatabaseListData.md +++ b/docs/reference/functions/useDatabaseListData.md @@ -8,7 +8,7 @@ > **useDatabaseListData**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`[] \| `null`\> -Defined in: [src/database.tsx:233](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L233) +Defined in: [src/database.tsx:167](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L167) Subscribe to a Realtime Database list and unwrap the snapshots into an array. diff --git a/docs/reference/functions/useDatabaseObject.md b/docs/reference/functions/useDatabaseObject.md index d3d340a9..1c7bf8f1 100644 --- a/docs/reference/functions/useDatabaseObject.md +++ b/docs/reference/functions/useDatabaseObject.md @@ -8,7 +8,7 @@ > **useDatabaseObject**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`QueryChange`](../interfaces/QueryChange.md) \| `T`\> -Defined in: [src/database.tsx:74](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L74) +Defined in: [src/database.tsx:54](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L54) Subscribe to a Realtime Database object diff --git a/docs/reference/functions/useDatabaseObjectData.md b/docs/reference/functions/useDatabaseObjectData.md index a2441131..213aa239 100644 --- a/docs/reference/functions/useDatabaseObjectData.md +++ b/docs/reference/functions/useDatabaseObjectData.md @@ -8,7 +8,7 @@ > **useDatabaseObjectData**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`\> -Defined in: [src/database.tsx:121](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L121) +Defined in: [src/database.tsx:93](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L93) Subscribe to a Realtime Database object and unwrap the snapshot into a plain object. diff --git a/docs/reference/functions/useFirestore.md b/docs/reference/functions/useFirestore.md index c27b3472..af8dd7c3 100644 --- a/docs/reference/functions/useFirestore.md +++ b/docs/reference/functions/useFirestore.md @@ -8,7 +8,7 @@ > **useFirestore**(): `Firestore` -Defined in: [src/sdk.tsx:136](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L136) +Defined in: [src/sdk.tsx:123](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L123) ## Returns diff --git a/docs/reference/functions/useFirestoreCollection.md b/docs/reference/functions/useFirestoreCollection.md index 515bc7fd..8479ccc1 100644 --- a/docs/reference/functions/useFirestoreCollection.md +++ b/docs/reference/functions/useFirestoreCollection.md @@ -8,7 +8,7 @@ > **useFirestoreCollection**\<`T`\>(`query`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`QuerySnapshot`\<`T`, `DocumentData`\>\> -Defined in: [src/firestore.tsx:276](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L276) +Defined in: [src/firestore.tsx:229](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L229) Subscribe to a Firestore collection diff --git a/docs/reference/functions/useFirestoreCollectionData.md b/docs/reference/functions/useFirestoreCollectionData.md index 1ffef7d6..7f1ce35e 100644 --- a/docs/reference/functions/useFirestoreCollectionData.md +++ b/docs/reference/functions/useFirestoreCollectionData.md @@ -8,7 +8,7 @@ > **useFirestoreCollectionData**\<`T`\>(`query`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T`[]\> -Defined in: [src/firestore.tsx:335](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L335) +Defined in: [src/firestore.tsx:293](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L293) Subscribe to a Firestore collection and unwrap the snapshot into an array. diff --git a/docs/reference/functions/useFirestoreDoc.md b/docs/reference/functions/useFirestoreDoc.md index 688c77f3..8f46119a 100644 --- a/docs/reference/functions/useFirestoreDoc.md +++ b/docs/reference/functions/useFirestoreDoc.md @@ -8,7 +8,7 @@ > **useFirestoreDoc**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`DocumentSnapshot`\<`T`, `DocumentData`\>\> -Defined in: [src/firestore.tsx:119](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L119) +Defined in: [src/firestore.tsx:105](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L105) Subscribe to Firestore Document changes diff --git a/docs/reference/functions/useFirestoreDocData.md b/docs/reference/functions/useFirestoreDocData.md index 36fd828d..934d8c57 100644 --- a/docs/reference/functions/useFirestoreDocData.md +++ b/docs/reference/functions/useFirestoreDocData.md @@ -8,7 +8,7 @@ > **useFirestoreDocData**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `undefined`\> -Defined in: [src/firestore.tsx:210](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L210) +Defined in: [src/firestore.tsx:197](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L197) Subscribe to Firestore Document changes and unwrap the document into a plain object diff --git a/docs/reference/functions/useFirestoreDocDataOnce.md b/docs/reference/functions/useFirestoreDocDataOnce.md index 5dc17368..45a942ee 100644 --- a/docs/reference/functions/useFirestoreDocDataOnce.md +++ b/docs/reference/functions/useFirestoreDocDataOnce.md @@ -8,7 +8,7 @@ > **useFirestoreDocDataOnce**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`T` \| `undefined`\> -Defined in: [src/firestore.tsx:229](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L229) +Defined in: [src/firestore.tsx:216](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L216) Get a Firestore document, unwrap the document into a plain object, and don't subscribe to changes diff --git a/docs/reference/functions/useFirestoreDocOnce.md b/docs/reference/functions/useFirestoreDocOnce.md index 5ee69d69..2f468ba7 100644 --- a/docs/reference/functions/useFirestoreDocOnce.md +++ b/docs/reference/functions/useFirestoreDocOnce.md @@ -8,7 +8,7 @@ > **useFirestoreDocOnce**\<`T`\>(`ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`DocumentSnapshot`\<`T`, `DocumentData`\>\> -Defined in: [src/firestore.tsx:180](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L180) +Defined in: [src/firestore.tsx:167](https://github.com/FirebaseExtended/reactfire/blob/main/src/firestore.tsx#L167) Get a firestore document and don't subscribe to changes diff --git a/docs/reference/functions/useFunctions.md b/docs/reference/functions/useFunctions.md index 55ee396b..8336b5c0 100644 --- a/docs/reference/functions/useFunctions.md +++ b/docs/reference/functions/useFunctions.md @@ -8,7 +8,7 @@ > **useFunctions**(): `Functions` -Defined in: [src/sdk.tsx:138](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L138) +Defined in: [src/sdk.tsx:125](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L125) ## Returns diff --git a/docs/reference/functions/useIdTokenResult.md b/docs/reference/functions/useIdTokenResult.md index 8382da79..88879c9b 100644 --- a/docs/reference/functions/useIdTokenResult.md +++ b/docs/reference/functions/useIdTokenResult.md @@ -10,7 +10,7 @@ > **useIdTokenResult**(`user`, `forceRefresh?`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`IdTokenResult`\> -Defined in: [src/auth.tsx:173](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L173) +Defined in: [src/auth.tsx:170](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L170) ### Parameters @@ -34,7 +34,7 @@ Defined in: [src/auth.tsx:173](https://github.com/FirebaseExtended/reactfire/blo > **useIdTokenResult**(`auth`, `forceRefresh?`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`IdTokenResult` \| `null`\> -Defined in: [src/auth.tsx:178](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L178) +Defined in: [src/auth.tsx:175](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L175) ### Parameters diff --git a/docs/reference/functions/usePerformance.md b/docs/reference/functions/usePerformance.md index 853a86ef..4170b36b 100644 --- a/docs/reference/functions/usePerformance.md +++ b/docs/reference/functions/usePerformance.md @@ -8,7 +8,7 @@ > **usePerformance**(): `FirebasePerformance` -Defined in: [src/sdk.tsx:140](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L140) +Defined in: [src/sdk.tsx:127](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L127) ## Returns diff --git a/docs/reference/functions/useRemoteConfig.md b/docs/reference/functions/useRemoteConfig.md index e96a0d83..50260eb3 100644 --- a/docs/reference/functions/useRemoteConfig.md +++ b/docs/reference/functions/useRemoteConfig.md @@ -8,7 +8,7 @@ > **useRemoteConfig**(): `RemoteConfig` -Defined in: [src/sdk.tsx:144](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L144) +Defined in: [src/sdk.tsx:131](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L131) ## Returns diff --git a/docs/reference/functions/useRemoteConfigAll.md b/docs/reference/functions/useRemoteConfigAll.md index 185c8b91..aa913546 100644 --- a/docs/reference/functions/useRemoteConfigAll.md +++ b/docs/reference/functions/useRemoteConfigAll.md @@ -8,7 +8,7 @@ > **useRemoteConfigAll**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`AllParameters`](../type-aliases/AllParameters.md)\> -Defined in: [src/remote-config.tsx:140](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L140) +Defined in: [src/remote-config.tsx:118](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L118) Convience method similar to useRemoteConfigValue. Returns allRemote Config parameters. diff --git a/docs/reference/functions/useRemoteConfigBoolean.md b/docs/reference/functions/useRemoteConfigBoolean.md index 88ab8744..093643e5 100644 --- a/docs/reference/functions/useRemoteConfigBoolean.md +++ b/docs/reference/functions/useRemoteConfigBoolean.md @@ -8,7 +8,7 @@ > **useRemoteConfigBoolean**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`boolean`\> -Defined in: [src/remote-config.tsx:130](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L130) +Defined in: [src/remote-config.tsx:108](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L108) Convience method similar to useRemoteConfigValue. Returns a `boolean` from a Remote Config parameter. diff --git a/docs/reference/functions/useRemoteConfigNumber.md b/docs/reference/functions/useRemoteConfigNumber.md index f700dd25..6d9bda92 100644 --- a/docs/reference/functions/useRemoteConfigNumber.md +++ b/docs/reference/functions/useRemoteConfigNumber.md @@ -8,7 +8,7 @@ > **useRemoteConfigNumber**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`number`\> -Defined in: [src/remote-config.tsx:120](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L120) +Defined in: [src/remote-config.tsx:98](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L98) Convience method similar to useRemoteConfigValue. Returns a `number` from a Remote Config parameter. diff --git a/docs/reference/functions/useRemoteConfigString.md b/docs/reference/functions/useRemoteConfigString.md index f7493c82..c92535d3 100644 --- a/docs/reference/functions/useRemoteConfigString.md +++ b/docs/reference/functions/useRemoteConfigString.md @@ -8,7 +8,7 @@ > **useRemoteConfigString**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`string`\> -Defined in: [src/remote-config.tsx:110](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L110) +Defined in: [src/remote-config.tsx:88](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L88) Convience method similar to useRemoteConfigValue. Returns a `string` from a Remote Config parameter. diff --git a/docs/reference/functions/useRemoteConfigValue.md b/docs/reference/functions/useRemoteConfigValue.md index 44af862c..687d1a1a 100644 --- a/docs/reference/functions/useRemoteConfigValue.md +++ b/docs/reference/functions/useRemoteConfigValue.md @@ -8,7 +8,7 @@ > **useRemoteConfigValue**(`key`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`Value`\> -Defined in: [src/remote-config.tsx:100](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L100) +Defined in: [src/remote-config.tsx:78](https://github.com/FirebaseExtended/reactfire/blob/main/src/remote-config.tsx#L78) Accepts a key and optionally a Remote Config instance. Returns a Remote Config Value. diff --git a/docs/reference/functions/useSigninCheck.md b/docs/reference/functions/useSigninCheck.md index 0d479acd..953abbdb 100644 --- a/docs/reference/functions/useSigninCheck.md +++ b/docs/reference/functions/useSigninCheck.md @@ -8,7 +8,7 @@ > **useSigninCheck**(`options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<[`SigninCheckResult`](../type-aliases/SigninCheckResult.md)\> -Defined in: [src/auth.tsx:343](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L343) +Defined in: [src/auth.tsx:334](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L334) Subscribe to the signed-in status of a user. diff --git a/docs/reference/functions/useStorage.md b/docs/reference/functions/useStorage.md index dcb593f8..40be7b3a 100644 --- a/docs/reference/functions/useStorage.md +++ b/docs/reference/functions/useStorage.md @@ -8,7 +8,7 @@ > **useStorage**(): `FirebaseStorage` -Defined in: [src/sdk.tsx:142](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L142) +Defined in: [src/sdk.tsx:129](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L129) ## Returns diff --git a/docs/reference/functions/useStorageDownloadURL.md b/docs/reference/functions/useStorageDownloadURL.md index 4e2a3a9b..519e1767 100644 --- a/docs/reference/functions/useStorageDownloadURL.md +++ b/docs/reference/functions/useStorageDownloadURL.md @@ -8,7 +8,7 @@ > **useStorageDownloadURL**\<`T`\>(`storageRef`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`string` \| `T`\> -Defined in: [src/storage.tsx:134](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L134) +Defined in: [src/storage.tsx:111](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L111) Subscribe to a storage ref's download URL @@ -24,6 +24,8 @@ Subscribe to a storage ref's download URL `StorageReference` +reference to the blob you want to download + ### options? [`ReactFireOptions`](../interfaces/ReactFireOptions.md)\<`T`\> diff --git a/docs/reference/functions/useStorageTask.md b/docs/reference/functions/useStorageTask.md index 442f780b..2efb832e 100644 --- a/docs/reference/functions/useStorageTask.md +++ b/docs/reference/functions/useStorageTask.md @@ -8,7 +8,7 @@ > **useStorageTask**\<`T`\>(`task`, `ref`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`UploadTaskSnapshot` \| `T`\> -Defined in: [src/storage.tsx:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L64) +Defined in: [src/storage.tsx:54](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L54) Subscribe to the progress of a storage task diff --git a/docs/reference/functions/useUser.md b/docs/reference/functions/useUser.md index 6c36eee3..68a79ed0 100644 --- a/docs/reference/functions/useUser.md +++ b/docs/reference/functions/useUser.md @@ -10,7 +10,7 @@ > **useUser**\<`T`\>(`options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`User` \| `null`\> -Defined in: [src/auth.tsx:108](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L108) +Defined in: [src/auth.tsx:102](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L102) Subscribe to Firebase auth state changes, including token refresh @@ -34,7 +34,7 @@ Subscribe to Firebase auth state changes, including token refresh > **useUser**\<`T`\>(`auth`, `options?`): [`ObservableStatus`](../interfaces/ObservableStatus.md)\<`User` \| `null`\> -Defined in: [src/auth.tsx:109](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L109) +Defined in: [src/auth.tsx:103](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L103) Subscribe to Firebase auth state changes, including token refresh diff --git a/docs/reference/interfaces/AuthCheckProps.md b/docs/reference/interfaces/AuthCheckProps.md index 876ade53..c6d66f26 100644 --- a/docs/reference/interfaces/AuthCheckProps.md +++ b/docs/reference/interfaces/AuthCheckProps.md @@ -6,7 +6,7 @@ # Interface: AuthCheckProps -Defined in: [src/auth.tsx:264](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L264) +Defined in: [src/auth.tsx:255](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L255) ## Properties @@ -14,7 +14,7 @@ Defined in: [src/auth.tsx:264](https://github.com/FirebaseExtended/reactfire/blo > `optional` **auth?**: `Auth` -Defined in: [src/auth.tsx:265](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L265) +Defined in: [src/auth.tsx:256](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L256) *** @@ -22,7 +22,7 @@ Defined in: [src/auth.tsx:265](https://github.com/FirebaseExtended/reactfire/blo > **children**: `ReactNode` -Defined in: [src/auth.tsx:267](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L267) +Defined in: [src/auth.tsx:258](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L258) *** @@ -30,7 +30,7 @@ Defined in: [src/auth.tsx:267](https://github.com/FirebaseExtended/reactfire/blo > **fallback**: `ReactNode` -Defined in: [src/auth.tsx:266](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L266) +Defined in: [src/auth.tsx:257](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L257) *** @@ -38,4 +38,4 @@ Defined in: [src/auth.tsx:266](https://github.com/FirebaseExtended/reactfire/blo > `optional` **requiredClaims?**: `Object` -Defined in: [src/auth.tsx:268](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L268) +Defined in: [src/auth.tsx:259](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L259) diff --git a/docs/reference/interfaces/ClaimCheckErrors.md b/docs/reference/interfaces/ClaimCheckErrors.md index 65d0fcb3..b034270e 100644 --- a/docs/reference/interfaces/ClaimCheckErrors.md +++ b/docs/reference/interfaces/ClaimCheckErrors.md @@ -6,7 +6,7 @@ # ~~Interface: ClaimCheckErrors~~ -Defined in: [src/auth.tsx:284](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L284) +Defined in: [src/auth.tsx:275](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L275) ## Deprecated diff --git a/docs/reference/interfaces/ClaimsCheckProps.md b/docs/reference/interfaces/ClaimsCheckProps.md index 36d2d5c2..da97e4e2 100644 --- a/docs/reference/interfaces/ClaimsCheckProps.md +++ b/docs/reference/interfaces/ClaimsCheckProps.md @@ -6,7 +6,7 @@ # ~~Interface: ClaimsCheckProps~~ -Defined in: [src/auth.tsx:274](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L274) +Defined in: [src/auth.tsx:265](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L265) ## Deprecated @@ -18,7 +18,7 @@ Use `` or `useIdTokenResult(auth)` instead. Will > **children**: `ReactNode` -Defined in: [src/auth.tsx:277](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L277) +Defined in: [src/auth.tsx:268](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L268) *** @@ -26,7 +26,7 @@ Defined in: [src/auth.tsx:277](https://github.com/FirebaseExtended/reactfire/blo > **fallback**: `ReactNode` -Defined in: [src/auth.tsx:276](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L276) +Defined in: [src/auth.tsx:267](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L267) *** @@ -34,7 +34,7 @@ Defined in: [src/auth.tsx:276](https://github.com/FirebaseExtended/reactfire/blo > **requiredClaims**: `object` -Defined in: [src/auth.tsx:278](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L278) +Defined in: [src/auth.tsx:269](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L269) #### Index Signature @@ -46,4 +46,4 @@ Defined in: [src/auth.tsx:278](https://github.com/FirebaseExtended/reactfire/blo > **user**: `User` -Defined in: [src/auth.tsx:275](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L275) +Defined in: [src/auth.tsx:266](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L266) diff --git a/docs/reference/interfaces/ClaimsValidator.md b/docs/reference/interfaces/ClaimsValidator.md index f532d3d9..686d9f05 100644 --- a/docs/reference/interfaces/ClaimsValidator.md +++ b/docs/reference/interfaces/ClaimsValidator.md @@ -6,7 +6,7 @@ # ~~Interface: ClaimsValidator()~~ -Defined in: [src/auth.tsx:322](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L322) +Defined in: [src/auth.tsx:313](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L313) ## Deprecated @@ -14,7 +14,7 @@ Will be removed in v5.0.0 along with `useSigninCheck`. > **ClaimsValidator**(`claims`): `object` -Defined in: [src/auth.tsx:323](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L323) +Defined in: [src/auth.tsx:314](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L314) ## Parameters diff --git a/docs/reference/interfaces/ObservableStatus.md b/docs/reference/interfaces/ObservableStatus.md index b260cf40..ec07572c 100644 --- a/docs/reference/interfaces/ObservableStatus.md +++ b/docs/reference/interfaces/ObservableStatus.md @@ -6,7 +6,7 @@ # Interface: ObservableStatus\ -Defined in: [src/useFirebaseStream.ts:82](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L82) +Defined in: [src/useFirebaseStream.ts:97](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L97) ## Type Parameters @@ -20,7 +20,7 @@ Defined in: [src/useFirebaseStream.ts:82](https://github.com/FirebaseExtended/re > **data**: `T` -Defined in: [src/useFirebaseStream.ts:112](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L112) +Defined in: [src/useFirebaseStream.ts:127](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L127) The most recent value. @@ -32,7 +32,7 @@ If `initialData` is passed in, the first value of `data` will be the value provi > **error**: `Error` \| `undefined` -Defined in: [src/useFirebaseStream.ts:116](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L116) +Defined in: [src/useFirebaseStream.ts:131](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L131) Any error that may have occurred in the underlying observable @@ -42,7 +42,7 @@ Any error that may have occurred in the underlying observable > `readonly` **experimental\_usable**: [`ReactFireUsable`](../type-aliases/ReactFireUsable.md)\<`T`\> -Defined in: [src/useFirebaseStream.ts:138](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L138) +Defined in: [src/useFirebaseStream.ts:153](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L153) A React 19 `Usable` that can be passed directly into `React.use()` (`const data = use(useFirestoreDoc(ref).experimental_usable)`). @@ -56,7 +56,7 @@ unwraps the latest live value on all subsequent renders without re-suspending. > **firstValuePromise**: `Promise`\<`void`\> -Defined in: [src/useFirebaseStream.ts:122](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L122) +Defined in: [src/useFirebaseStream.ts:137](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L137) Promise that resolves after first emit from observable @@ -70,7 +70,7 @@ Pass `.experimental_usable` (or `.usable`) to `React.use()` instead. Will be rem > **hasEmitted**: `boolean` -Defined in: [src/useFirebaseStream.ts:100](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L100) +Defined in: [src/useFirebaseStream.ts:115](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L115) Indicates whether the hook has emitted a value at some point @@ -86,7 +86,7 @@ Check `status === 'success'` or pass `.experimental_usable` to `React.use()` ins > **isComplete**: `boolean` -Defined in: [src/useFirebaseStream.ts:106](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L106) +Defined in: [src/useFirebaseStream.ts:121](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L121) If this is `true`, the hook will be emitting no further items. @@ -100,7 +100,7 @@ Will be removed in v5.0.0. > **status**: `"loading"` \| `"error"` \| `"success"` -Defined in: [src/useFirebaseStream.ts:92](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L92) +Defined in: [src/useFirebaseStream.ts:107](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L107) The loading status. @@ -116,7 +116,7 @@ If `initialData` is passed in, this will skip `loading` and go straight to `succ > `readonly` **usable**: [`ReactFireUsable`](../type-aliases/ReactFireUsable.md)\<`T`\> -Defined in: [src/useFirebaseStream.ts:130](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L130) +Defined in: [src/useFirebaseStream.ts:145](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L145) A React 19 `Usable` that can be passed directly into `React.use()` (`const data = use(useFirestoreDoc(ref).usable)`). diff --git a/docs/reference/interfaces/QueryChange.md b/docs/reference/interfaces/QueryChange.md index 27ce86b1..4783147b 100644 --- a/docs/reference/interfaces/QueryChange.md +++ b/docs/reference/interfaces/QueryChange.md @@ -6,7 +6,7 @@ # ~~Interface: QueryChange~~ -Defined in: [src/database.tsx:27](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L27) +Defined in: [src/database.tsx:28](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L28) ## Deprecated @@ -18,7 +18,7 @@ Legacy rxfire change wrapper. In v5.0.0, `useDatabaseObject` returns `DataSnapsh > **event**: [`ListenEvent`](../enumerations/ListenEvent.md) -Defined in: [src/database.tsx:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L30) +Defined in: [src/database.tsx:31](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L31) *** @@ -26,7 +26,7 @@ Defined in: [src/database.tsx:30](https://github.com/FirebaseExtended/reactfire/ > **prevKey**: `string` \| `null` \| `undefined` -Defined in: [src/database.tsx:29](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L29) +Defined in: [src/database.tsx:30](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L30) *** @@ -34,4 +34,4 @@ Defined in: [src/database.tsx:29](https://github.com/FirebaseExtended/reactfire/ > **snapshot**: `DataSnapshot` -Defined in: [src/database.tsx:28](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L28) +Defined in: [src/database.tsx:29](https://github.com/FirebaseExtended/reactfire/blob/main/src/database.tsx#L29) diff --git a/docs/reference/interfaces/ReactFireOptions.md b/docs/reference/interfaces/ReactFireOptions.md index bba0f2fe..0f426010 100644 --- a/docs/reference/interfaces/ReactFireOptions.md +++ b/docs/reference/interfaces/ReactFireOptions.md @@ -6,7 +6,7 @@ # Interface: ReactFireOptions\ -Defined in: [src/useFirebaseStream.ts:32](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L32) +Defined in: [src/useFirebaseStream.ts:47](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L47) ## Extended by @@ -24,7 +24,7 @@ Defined in: [src/useFirebaseStream.ts:32](https://github.com/FirebaseExtended/re > `optional` **idField?**: `string` -Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) +Defined in: [src/useFirebaseStream.ts:51](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L51) #### Deprecated @@ -36,7 +36,7 @@ Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/re > `optional` **includeMetadataChanges?**: `boolean` -Defined in: [src/useFirebaseStream.ts:53](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L53) +Defined in: [src/useFirebaseStream.ts:68](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L68) Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). @@ -46,7 +46,7 @@ Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFires > `optional` **initialData?**: `any` -Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L37) +Defined in: [src/useFirebaseStream.ts:52](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L52) *** @@ -54,7 +54,7 @@ Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/re > `optional` **once?**: `boolean` -Defined in: [src/useFirebaseStream.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L49) +Defined in: [src/useFirebaseStream.ts:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L64) Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). @@ -64,7 +64,7 @@ Resolve the initial read once and do not keep a real-time listener open (`useFir > `optional` **startWithValue?**: `any` -Defined in: [src/useFirebaseStream.ts:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L41) +Defined in: [src/useFirebaseStream.ts:56](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L56) #### Deprecated @@ -76,7 +76,7 @@ use `initialData` instead > `optional` **suspense?**: `boolean` -Defined in: [src/useFirebaseStream.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L45) +Defined in: [src/useFirebaseStream.ts:60](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L60) #### Deprecated diff --git a/docs/reference/interfaces/SignInCheckOptionsBasic.md b/docs/reference/interfaces/SignInCheckOptionsBasic.md index 84b2b699..03480198 100644 --- a/docs/reference/interfaces/SignInCheckOptionsBasic.md +++ b/docs/reference/interfaces/SignInCheckOptionsBasic.md @@ -6,7 +6,7 @@ # ~~Interface: SignInCheckOptionsBasic~~ -Defined in: [src/auth.tsx:308](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L308) +Defined in: [src/auth.tsx:299](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L299) ## Deprecated @@ -27,7 +27,7 @@ Will be removed in v5.0.0 along with `useSigninCheck`. > `optional` **forceRefresh?**: `boolean` -Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L309) +Defined in: [src/auth.tsx:300](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L300) *** @@ -35,7 +35,7 @@ Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blo > `optional` **idField?**: `string` -Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) +Defined in: [src/useFirebaseStream.ts:51](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L51) #### Deprecated @@ -51,7 +51,7 @@ Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/re > `optional` **includeMetadataChanges?**: `boolean` -Defined in: [src/useFirebaseStream.ts:53](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L53) +Defined in: [src/useFirebaseStream.ts:68](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L68) Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). @@ -65,7 +65,7 @@ Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFires > `optional` **initialData?**: `any` -Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L37) +Defined in: [src/useFirebaseStream.ts:52](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L52) #### Inherited from @@ -77,7 +77,7 @@ Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/re > `optional` **once?**: `boolean` -Defined in: [src/useFirebaseStream.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L49) +Defined in: [src/useFirebaseStream.ts:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L64) Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). @@ -91,7 +91,7 @@ Resolve the initial read once and do not keep a real-time listener open (`useFir > `optional` **startWithValue?**: `any` -Defined in: [src/useFirebaseStream.ts:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L41) +Defined in: [src/useFirebaseStream.ts:56](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L56) #### Deprecated @@ -107,7 +107,7 @@ use `initialData` instead > `optional` **suspense?**: `boolean` -Defined in: [src/useFirebaseStream.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L45) +Defined in: [src/useFirebaseStream.ts:60](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L60) #### Deprecated diff --git a/docs/reference/interfaces/SignInCheckOptionsClaimsObject.md b/docs/reference/interfaces/SignInCheckOptionsClaimsObject.md index fc8b606e..76332ea4 100644 --- a/docs/reference/interfaces/SignInCheckOptionsClaimsObject.md +++ b/docs/reference/interfaces/SignInCheckOptionsClaimsObject.md @@ -6,7 +6,7 @@ # ~~Interface: SignInCheckOptionsClaimsObject~~ -Defined in: [src/auth.tsx:315](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L315) +Defined in: [src/auth.tsx:306](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L306) ## Deprecated @@ -22,7 +22,7 @@ Will be removed in v5.0.0 along with `useSigninCheck`. > `optional` **forceRefresh?**: `boolean` -Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L309) +Defined in: [src/auth.tsx:300](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L300) #### Inherited from @@ -34,7 +34,7 @@ Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blo > `optional` **idField?**: `string` -Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) +Defined in: [src/useFirebaseStream.ts:51](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L51) #### Deprecated @@ -50,7 +50,7 @@ Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/re > `optional` **includeMetadataChanges?**: `boolean` -Defined in: [src/useFirebaseStream.ts:53](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L53) +Defined in: [src/useFirebaseStream.ts:68](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L68) Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). @@ -64,7 +64,7 @@ Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFires > `optional` **initialData?**: `any` -Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L37) +Defined in: [src/useFirebaseStream.ts:52](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L52) #### Inherited from @@ -76,7 +76,7 @@ Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/re > `optional` **once?**: `boolean` -Defined in: [src/useFirebaseStream.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L49) +Defined in: [src/useFirebaseStream.ts:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L64) Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). @@ -90,7 +90,7 @@ Resolve the initial read once and do not keep a real-time listener open (`useFir > **requiredClaims**: `ParsedToken` -Defined in: [src/auth.tsx:316](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L316) +Defined in: [src/auth.tsx:307](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L307) *** @@ -98,7 +98,7 @@ Defined in: [src/auth.tsx:316](https://github.com/FirebaseExtended/reactfire/blo > `optional` **startWithValue?**: `any` -Defined in: [src/useFirebaseStream.ts:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L41) +Defined in: [src/useFirebaseStream.ts:56](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L56) #### Deprecated @@ -114,7 +114,7 @@ use `initialData` instead > `optional` **suspense?**: `boolean` -Defined in: [src/useFirebaseStream.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L45) +Defined in: [src/useFirebaseStream.ts:60](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L60) #### Deprecated diff --git a/docs/reference/interfaces/SignInCheckOptionsClaimsValidator.md b/docs/reference/interfaces/SignInCheckOptionsClaimsValidator.md index 39585c22..8bcf14fa 100644 --- a/docs/reference/interfaces/SignInCheckOptionsClaimsValidator.md +++ b/docs/reference/interfaces/SignInCheckOptionsClaimsValidator.md @@ -6,7 +6,7 @@ # ~~Interface: SignInCheckOptionsClaimsValidator~~ -Defined in: [src/auth.tsx:332](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L332) +Defined in: [src/auth.tsx:323](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L323) ## Deprecated @@ -22,7 +22,7 @@ Will be removed in v5.0.0 along with `useSigninCheck`. > `optional` **forceRefresh?**: `boolean` -Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L309) +Defined in: [src/auth.tsx:300](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L300) #### Inherited from @@ -34,7 +34,7 @@ Defined in: [src/auth.tsx:309](https://github.com/FirebaseExtended/reactfire/blo > `optional` **idField?**: `string` -Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L36) +Defined in: [src/useFirebaseStream.ts:51](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L51) #### Deprecated @@ -50,7 +50,7 @@ Defined in: [src/useFirebaseStream.ts:36](https://github.com/FirebaseExtended/re > `optional` **includeMetadataChanges?**: `boolean` -Defined in: [src/useFirebaseStream.ts:53](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L53) +Defined in: [src/useFirebaseStream.ts:68](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L68) Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFirestoreDoc` / `useFirestoreCollection`). @@ -64,7 +64,7 @@ Emit when snapshot metadata (`hasPendingWrites`, `fromCache`) changes (`useFires > `optional` **initialData?**: `any` -Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L37) +Defined in: [src/useFirebaseStream.ts:52](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L52) #### Inherited from @@ -76,7 +76,7 @@ Defined in: [src/useFirebaseStream.ts:37](https://github.com/FirebaseExtended/re > `optional` **once?**: `boolean` -Defined in: [src/useFirebaseStream.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L49) +Defined in: [src/useFirebaseStream.ts:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L64) Resolve the initial read once and do not keep a real-time listener open (`useFirestoreDoc` / `useFirestoreCollection`). @@ -90,7 +90,7 @@ Resolve the initial read once and do not keep a real-time listener open (`useFir > `optional` **startWithValue?**: `any` -Defined in: [src/useFirebaseStream.ts:41](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L41) +Defined in: [src/useFirebaseStream.ts:56](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L56) #### Deprecated @@ -106,7 +106,7 @@ use `initialData` instead > `optional` **suspense?**: `boolean` -Defined in: [src/useFirebaseStream.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L45) +Defined in: [src/useFirebaseStream.ts:60](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L60) #### Deprecated @@ -122,4 +122,4 @@ Pass `hook().experimental_usable` (or `hook().usable`) to `React.use()` instead > **validateCustomClaims**: [`ClaimsValidator`](ClaimsValidator.md) -Defined in: [src/auth.tsx:333](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L333) +Defined in: [src/auth.tsx:324](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L324) diff --git a/docs/reference/type-aliases/ReactFireUsable.md b/docs/reference/type-aliases/ReactFireUsable.md index 8b24c337..7bc083f5 100644 --- a/docs/reference/type-aliases/ReactFireUsable.md +++ b/docs/reference/type-aliases/ReactFireUsable.md @@ -8,7 +8,7 @@ > **ReactFireUsable**\<`T`\> = `Promise`\<`T`\> -Defined in: [src/useFirebaseStream.ts:9](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L9) +Defined in: [src/useFirebaseStream.ts:24](https://github.com/FirebaseExtended/reactfire/blob/main/src/useFirebaseStream.ts#L24) A thenable compatible with React 19's `React.use()` while remaining structurally valid under `@types/react` 16, 17, 18, and 19. diff --git a/docs/reference/type-aliases/SigninCheckResult.md b/docs/reference/type-aliases/SigninCheckResult.md index a1ec562b..deeb5cc5 100644 --- a/docs/reference/type-aliases/SigninCheckResult.md +++ b/docs/reference/type-aliases/SigninCheckResult.md @@ -8,7 +8,7 @@ > **SigninCheckResult** = \{ `errors`: \{ \}; `hasRequiredClaims`: `false`; `signedIn`: `false`; `user`: `null`; \} \| \{ `errors`: [`ClaimCheckErrors`](../interfaces/ClaimCheckErrors.md); `hasRequiredClaims`: `boolean`; `signedIn`: `true`; `user`: `User`; \} -Defined in: [src/auth.tsx:291](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L291) +Defined in: [src/auth.tsx:282](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L282) ## Deprecated diff --git a/docs/reference/type-aliases/StorageImageProps.md b/docs/reference/type-aliases/StorageImageProps.md index 80c2f89d..fd6d5f92 100644 --- a/docs/reference/type-aliases/StorageImageProps.md +++ b/docs/reference/type-aliases/StorageImageProps.md @@ -8,7 +8,7 @@ > **StorageImageProps** = `object` -Defined in: [src/storage.tsx:165](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L165) +Defined in: [src/storage.tsx:136](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L136) ## Deprecated @@ -20,7 +20,7 @@ Use `useStorageDownloadURL(ref)` with a standard `` element instead. Will > `optional` **placeHolder?**: `React.ReactNode` -Defined in: [src/storage.tsx:169](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L169) +Defined in: [src/storage.tsx:140](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L140) *** @@ -28,7 +28,7 @@ Defined in: [src/storage.tsx:169](https://github.com/FirebaseExtended/reactfire/ > `optional` **storage?**: `FirebaseStorage` -Defined in: [src/storage.tsx:167](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L167) +Defined in: [src/storage.tsx:138](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L138) *** @@ -36,7 +36,7 @@ Defined in: [src/storage.tsx:167](https://github.com/FirebaseExtended/reactfire/ > **storagePath**: `string` -Defined in: [src/storage.tsx:166](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L166) +Defined in: [src/storage.tsx:137](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L137) *** @@ -44,4 +44,4 @@ Defined in: [src/storage.tsx:166](https://github.com/FirebaseExtended/reactfire/ > `optional` **suspense?**: `boolean` -Defined in: [src/storage.tsx:168](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L168) +Defined in: [src/storage.tsx:139](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L139) diff --git a/docs/reference/variables/AnalyticsProvider.md b/docs/reference/variables/AnalyticsProvider.md index 4b14f6a0..8ba1d924 100644 --- a/docs/reference/variables/AnalyticsProvider.md +++ b/docs/reference/variables/AnalyticsProvider.md @@ -8,7 +8,7 @@ > `const` **AnalyticsProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:113](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L113) +Defined in: [src/sdk.tsx:100](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L100) ## Parameters diff --git a/docs/reference/variables/AppCheckProvider.md b/docs/reference/variables/AppCheckProvider.md index 38b77345..90a39e18 100644 --- a/docs/reference/variables/AppCheckProvider.md +++ b/docs/reference/variables/AppCheckProvider.md @@ -8,7 +8,7 @@ > `const` **AppCheckProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:109](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L109) +Defined in: [src/sdk.tsx:96](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L96) ## Parameters diff --git a/docs/reference/variables/AuthProvider.md b/docs/reference/variables/AuthProvider.md index fbf0ed1f..86bf6ef8 100644 --- a/docs/reference/variables/AuthProvider.md +++ b/docs/reference/variables/AuthProvider.md @@ -8,7 +8,7 @@ > `const` **AuthProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:111](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L111) +Defined in: [src/sdk.tsx:98](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L98) ## Parameters diff --git a/docs/reference/variables/DatabaseProvider.md b/docs/reference/variables/DatabaseProvider.md index 124333fc..3aadaed2 100644 --- a/docs/reference/variables/DatabaseProvider.md +++ b/docs/reference/variables/DatabaseProvider.md @@ -8,7 +8,7 @@ > `const` **DatabaseProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:115](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L115) +Defined in: [src/sdk.tsx:102](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L102) ## Parameters diff --git a/docs/reference/variables/FirestoreProvider.md b/docs/reference/variables/FirestoreProvider.md index 837d83b4..26b21a89 100644 --- a/docs/reference/variables/FirestoreProvider.md +++ b/docs/reference/variables/FirestoreProvider.md @@ -8,7 +8,7 @@ > `const` **FirestoreProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:117](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L117) +Defined in: [src/sdk.tsx:104](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L104) ## Parameters diff --git a/docs/reference/variables/FunctionsProvider.md b/docs/reference/variables/FunctionsProvider.md index 95930e69..6cd4eeee 100644 --- a/docs/reference/variables/FunctionsProvider.md +++ b/docs/reference/variables/FunctionsProvider.md @@ -8,7 +8,7 @@ > `const` **FunctionsProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:119](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L119) +Defined in: [src/sdk.tsx:106](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L106) ## Parameters diff --git a/docs/reference/variables/PerformanceProvider.md b/docs/reference/variables/PerformanceProvider.md index b6bd02fa..6fbc1063 100644 --- a/docs/reference/variables/PerformanceProvider.md +++ b/docs/reference/variables/PerformanceProvider.md @@ -8,7 +8,7 @@ > `const` **PerformanceProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:121](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L121) +Defined in: [src/sdk.tsx:108](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L108) ## Parameters diff --git a/docs/reference/variables/RemoteConfigProvider.md b/docs/reference/variables/RemoteConfigProvider.md index c259c6e6..f1462080 100644 --- a/docs/reference/variables/RemoteConfigProvider.md +++ b/docs/reference/variables/RemoteConfigProvider.md @@ -8,7 +8,7 @@ > `const` **RemoteConfigProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:125](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L125) +Defined in: [src/sdk.tsx:112](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L112) ## Parameters diff --git a/docs/reference/variables/StorageProvider.md b/docs/reference/variables/StorageProvider.md index 91d964cd..01fa67c7 100644 --- a/docs/reference/variables/StorageProvider.md +++ b/docs/reference/variables/StorageProvider.md @@ -8,7 +8,7 @@ > `const` **StorageProvider**: (`props`) => `ReactElement` -Defined in: [src/sdk.tsx:123](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L123) +Defined in: [src/sdk.tsx:110](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L110) ## Parameters diff --git a/docs/reference/variables/useInitAnalytics.md b/docs/reference/variables/useInitAnalytics.md index 1bf7317d..48b1b50f 100644 --- a/docs/reference/variables/useInitAnalytics.md +++ b/docs/reference/variables/useInitAnalytics.md @@ -8,7 +8,7 @@ > `const` **useInitAnalytics**: `InitSdkHook`\<`Analytics`\> -Defined in: [src/sdk.tsx:156](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L156) +Defined in: [src/sdk.tsx:143](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L143) ## Deprecated diff --git a/docs/reference/variables/useInitAppCheck.md b/docs/reference/variables/useInitAppCheck.md index 5d9eb0a4..6b2b94e3 100644 --- a/docs/reference/variables/useInitAppCheck.md +++ b/docs/reference/variables/useInitAppCheck.md @@ -8,7 +8,7 @@ > `const` **useInitAppCheck**: `InitSdkHook`\<`AppCheck`\> -Defined in: [src/sdk.tsx:152](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L152) +Defined in: [src/sdk.tsx:139](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L139) ## Deprecated diff --git a/docs/reference/variables/useInitAuth.md b/docs/reference/variables/useInitAuth.md index 948f6123..48f66013 100644 --- a/docs/reference/variables/useInitAuth.md +++ b/docs/reference/variables/useInitAuth.md @@ -8,7 +8,7 @@ > `const` **useInitAuth**: `InitSdkHook`\<`Auth`\> -Defined in: [src/sdk.tsx:154](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L154) +Defined in: [src/sdk.tsx:141](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L141) ## Deprecated diff --git a/docs/reference/variables/useInitDatabase.md b/docs/reference/variables/useInitDatabase.md index a4944054..22b3b4cf 100644 --- a/docs/reference/variables/useInitDatabase.md +++ b/docs/reference/variables/useInitDatabase.md @@ -8,7 +8,7 @@ > `const` **useInitDatabase**: `InitSdkHook`\<`Database`\> -Defined in: [src/sdk.tsx:158](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L158) +Defined in: [src/sdk.tsx:145](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L145) ## Deprecated diff --git a/docs/reference/variables/useInitFirestore.md b/docs/reference/variables/useInitFirestore.md index 8dfe35fd..9d050bc0 100644 --- a/docs/reference/variables/useInitFirestore.md +++ b/docs/reference/variables/useInitFirestore.md @@ -8,7 +8,7 @@ > `const` **useInitFirestore**: `InitSdkHook`\<`Firestore`\> -Defined in: [src/sdk.tsx:160](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L160) +Defined in: [src/sdk.tsx:147](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L147) ## Deprecated diff --git a/docs/reference/variables/useInitFunctions.md b/docs/reference/variables/useInitFunctions.md index bc3cfee1..83113059 100644 --- a/docs/reference/variables/useInitFunctions.md +++ b/docs/reference/variables/useInitFunctions.md @@ -8,7 +8,7 @@ > `const` **useInitFunctions**: `InitSdkHook`\<`Functions`\> -Defined in: [src/sdk.tsx:162](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L162) +Defined in: [src/sdk.tsx:149](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L149) ## Deprecated diff --git a/docs/reference/variables/useInitPerformance.md b/docs/reference/variables/useInitPerformance.md index ac42980c..6c1f0833 100644 --- a/docs/reference/variables/useInitPerformance.md +++ b/docs/reference/variables/useInitPerformance.md @@ -8,7 +8,7 @@ > `const` **useInitPerformance**: `InitSdkHook`\<`FirebasePerformance`\> -Defined in: [src/sdk.tsx:164](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L164) +Defined in: [src/sdk.tsx:151](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L151) ## Deprecated diff --git a/docs/reference/variables/useInitRemoteConfig.md b/docs/reference/variables/useInitRemoteConfig.md index 238c54f5..867a4bf7 100644 --- a/docs/reference/variables/useInitRemoteConfig.md +++ b/docs/reference/variables/useInitRemoteConfig.md @@ -8,7 +8,7 @@ > `const` **useInitRemoteConfig**: `InitSdkHook`\<`RemoteConfig`\> -Defined in: [src/sdk.tsx:167](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L167) +Defined in: [src/sdk.tsx:154](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L154) ## Deprecated diff --git a/docs/reference/variables/useInitStorage.md b/docs/reference/variables/useInitStorage.md index 1a80c0b5..d5dfbd84 100644 --- a/docs/reference/variables/useInitStorage.md +++ b/docs/reference/variables/useInitStorage.md @@ -8,7 +8,7 @@ > `const` **useInitStorage**: `InitSdkHook`\<`FirebaseStorage`\> -Defined in: [src/sdk.tsx:170](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L170) +Defined in: [src/sdk.tsx:157](https://github.com/FirebaseExtended/reactfire/blob/main/src/sdk.tsx#L157) ## Deprecated diff --git a/package-lock.json b/package-lock.json index a0cf45e4..7f078203 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "node": ">=14" }, "peerDependencies": { - "firebase": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || next", + "firebase": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || next", "react": ">=16 || experimental" } }, diff --git a/package.json b/package.json index ec063c1d..ae9f8369 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "docs:fork": "typedoc --options typedoc.json --gitRemote upstream && markdown-toc -i docs/use.md" }, "peerDependencies": { - "firebase": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || next", + "firebase": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || next", "react": ">=16 || experimental" }, "husky": { diff --git a/src/SuspenseSubject.ts b/src/SuspenseSubject.ts index 18146dc0..73b3b444 100644 --- a/src/SuspenseSubject.ts +++ b/src/SuspenseSubject.ts @@ -59,7 +59,8 @@ export class SuspenseSubject extends Subject { }, }), catchError(() => empty()), - shareReplay(1) + // `refCount: true` detaches the source (the Firestore/RTDB listener) when the last subscriber leaves. + shareReplay({ bufferSize: 1, refCount: true }) ); // warm up the observable this._warmupSubscription = this._innerObservable.subscribe(); @@ -144,6 +145,10 @@ export class SuspenseSubject extends Subject { clearTimeout(this._timeoutHandler); } this._innerSubscriber = this._innerObservable.subscribe(subscriber); + // A real subscriber now keeps the source alive; drop the warm-up subscription so unmounting releases it. + if (this._warmupSubscription) { + this._warmupSubscription.unsubscribe(); + } return this._innerSubscriber; } diff --git a/src/auth.tsx b/src/auth.tsx index 7e2cce78..9d313054 100644 --- a/src/auth.tsx +++ b/src/auth.tsx @@ -9,7 +9,9 @@ import { ReactFireOptions, ReactFireUsable, attachUsable, + clearAllInFlight, createFulfilledUsable, + createInFlightCache, getInitialDataOption, hasInitialDataOption, instrumentPromise, @@ -21,69 +23,61 @@ import type { Auth, User, IdTokenResult } from 'firebase/auth'; type Claims = IdTokenResult['claims']; const authReadyWeakMap = new WeakMap>(); +const lastUidByAuth = new WeakMap(); + +function noteUserForAuth(auth: Auth, user: User | null): void { + const nextUid = user?.uid ?? null; + const prevUid = lastUidByAuth.get(auth); + if (prevUid !== undefined && prevUid !== nextUid) { + clearAllInFlight(); + } + lastUidByAuth.set(auth, nextUid); +} function getAuthReady(auth: Auth): InstrumentedPromise { let promise = authReadyWeakMap.get(auth); if (!promise) { if ((auth as { _isInitialized?: boolean })._isInitialized || auth.currentUser) { + noteUserForAuth(auth, auth.currentUser); promise = createFulfilledUsable(auth.currentUser); } else { - promise = instrumentPromise(auth.authStateReady().then(() => auth.currentUser)); + promise = instrumentPromise( + auth.authStateReady().then(() => { + noteUserForAuth(auth, auth.currentUser); + return auth.currentUser; + }) + ); } authReadyWeakMap.set(auth, promise); } else if (promise.status === 'fulfilled' && promise.value !== auth.currentUser) { + noteUserForAuth(auth, auth.currentUser); promise = createFulfilledUsable(auth.currentUser); authReadyWeakMap.set(auth, promise); } return promise; } -const IN_FLIGHT_EVICT_MS = 30_000; -const inFlightTokenPromises = new WeakMap>>(); -const inFlightAuthTokenPromises = new WeakMap>>(); +interface UserTokenKey { + user: User; + forceRefresh: boolean; +} -function getOrStartTokenPromise(user: User, forceRefresh: boolean): InstrumentedPromise { - let byRefresh = inFlightTokenPromises.get(user); - if (!byRefresh) { - byRefresh = new Map(); - inFlightTokenPromises.set(user, byRefresh); - } - let promise = byRefresh.get(forceRefresh); - if (!promise) { - promise = instrumentPromise(user.getIdTokenResult(forceRefresh)); - byRefresh.set(forceRefresh, promise); - const cleanup = () => - setTimeout(() => { - if (byRefresh?.get(forceRefresh) === promise) { - byRefresh.delete(forceRefresh); - } - }, IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - } - return promise; +interface AuthTokenKey { + auth: Auth; + uid: string | null; + forceRefresh: boolean; } -function getOrStartAuthTokenPromise(auth: Auth, forceRefresh: boolean): InstrumentedPromise { - let byRefresh = inFlightAuthTokenPromises.get(auth); - if (!byRefresh) { - byRefresh = new Map(); - inFlightAuthTokenPromises.set(auth, byRefresh); - } - let promise = byRefresh.get(forceRefresh); - if (!promise) { - promise = instrumentPromise( - getAuthReady(auth).then((user) => (user ? user.getIdTokenResult(forceRefresh) : null)) - ); - byRefresh.set(forceRefresh, promise); - const cleanup = () => - setTimeout(() => { - if (byRefresh?.get(forceRefresh) === promise) { - byRefresh.delete(forceRefresh); - } - }, IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - } - return promise; +const userTokenCache = createInFlightCache( + (a, b) => a.user === b.user && a.forceRefresh === b.forceRefresh +); + +const authTokenCache = createInFlightCache( + (a, b) => a.auth === b.auth && a.uid === b.uid && a.forceRefresh === b.forceRefresh +); + +function getOrStartTokenPromise(user: User, forceRefresh: boolean): InstrumentedPromise { + return userTokenCache.handle({ user, forceRefresh }, () => user.getIdTokenResult(forceRefresh)).getOrStart(); } function isAuthInstance(val: unknown): val is Auth { @@ -122,9 +116,11 @@ export function useUser( return useFirebaseStream({ ref: auth, - peekInFlight: () => authReadyWeakMap.get(auth), - getOrStartInFlight: () => getAuthReady(auth), - clearInFlight: () => {}, + inFlight: { + peek: () => authReadyWeakMap.get(auth), + getOrStart: () => getAuthReady(auth), + clear: () => {}, + }, getSyncInitialValue: () => { const isReady = Boolean(auth.currentUser) || authReady.status === 'fulfilled'; return isReady @@ -150,6 +146,7 @@ export function useUser( const unsubscribe = onIdTokenChanged( auth, (nextUser) => { + noteUserForAuth(auth, nextUser); if (active) { onNext(nextUser, false); } @@ -190,52 +187,44 @@ export function useIdTokenResult( } const isAuth = isAuthInstance(userOrAuth); - - return useFirebaseStream<{ target: User | Auth; forceRefresh: boolean }, any>({ - ref: { target: userOrAuth, forceRefresh }, - isEqual: (a, b) => a.target === b.target && a.forceRefresh === b.forceRefresh, - peekInFlight: () => - isAuth - ? inFlightAuthTokenPromises.get(userOrAuth)?.get(forceRefresh) - : inFlightTokenPromises.get(userOrAuth)?.get(forceRefresh), - getOrStartInFlight: () => - isAuth - ? getOrStartAuthTokenPromise(userOrAuth, forceRefresh) - : getOrStartTokenPromise(userOrAuth, forceRefresh), - clearInFlight: (expectedPromise) => { - if (isAuth) { - const byRefresh = inFlightAuthTokenPromises.get(userOrAuth); - if (byRefresh && (!expectedPromise || byRefresh.get(forceRefresh) === expectedPromise)) { - byRefresh.delete(forceRefresh); - } - } else { - const byRefresh = inFlightTokenPromises.get(userOrAuth); - if (byRefresh && (!expectedPromise || byRefresh.get(forceRefresh) === expectedPromise)) { - byRefresh.delete(forceRefresh); - } - } - }, + const uid = isAuth ? (userOrAuth.currentUser?.uid ?? null) : userOrAuth.uid; + + return useFirebaseStream<{ target: User | Auth; uid: string | null; forceRefresh: boolean }, any>({ + ref: { target: userOrAuth, uid, forceRefresh }, + isEqual: (a, b) => a.target === b.target && a.uid === b.uid && a.forceRefresh === b.forceRefresh, + inFlight: isAuth + ? authTokenCache.handle({ auth: userOrAuth, uid, forceRefresh }, () => + getAuthReady(userOrAuth).then((user) => (user ? user.getIdTokenResult(forceRefresh) : null)) + ) + : userTokenCache.handle({ user: userOrAuth, forceRefresh }, () => userOrAuth.getIdTokenResult(forceRefresh)), subscribe: (onNext, onError, inFlightPromise) => { if (isAuth) { let active = true; let lastUser: User | null = null; + let seq = 0; const unsubscribe = onIdTokenChanged( userOrAuth, (nextUser) => { + noteUserForAuth(userOrAuth, nextUser); + const currentSeq = ++seq; if (!active) return; if (!nextUser) { lastUser = null; onNext(null, false); return; } - const shouldForce = nextUser !== lastUser && forceRefresh; + const isNewUser = nextUser !== lastUser; lastUser = nextUser; - getOrStartTokenPromise(nextUser, shouldForce).then( + const tokenPromise = + currentSeq === 1 && inFlightPromise && nextUser.uid === uid + ? (inFlightPromise as Promise) + : nextUser.getIdTokenResult(isNewUser && forceRefresh); + tokenPromise.then( (res) => { - if (active) onNext(res, false); + if (active && currentSeq === seq) onNext(res, false); }, (err) => { - if (active) onError(err); + if (active && currentSeq === seq) onError(err); } ); }, @@ -249,7 +238,9 @@ export function useIdTokenResult( }; } - const tokenPromise = (inFlightPromise as InstrumentedPromise | undefined) ?? getOrStartTokenPromise(userOrAuth, forceRefresh); + const tokenPromise = + (inFlightPromise as InstrumentedPromise | undefined) ?? + getOrStartTokenPromise(userOrAuth, forceRefresh); tokenPromise.then( (result) => onNext(result, true), (error: Error) => onError(error) @@ -379,15 +370,18 @@ export function useSigninCheck( const tokenReqRef = React.useRef<{ user: User; forceRefresh: boolean; promise: InstrumentedPromise } | null>(null); if (!hasInitialData && currentUser && needsClaimsCheck) { + const tokenHandle = userTokenCache.handle({ user: currentUser, forceRefresh }, () => + currentUser.getIdTokenResult(forceRefresh) + ); const existingTokenPromise = tokenReqRef.current && tokenReqRef.current.user === currentUser && tokenReqRef.current.forceRefresh === forceRefresh ? tokenReqRef.current.promise - : inFlightTokenPromises.get(currentUser)?.get(forceRefresh); + : tokenHandle.peek(); if (suspenseEnabled) { tokenReqRef.current = { user: currentUser, forceRefresh, - promise: existingTokenPromise ?? getOrStartTokenPromise(currentUser, forceRefresh), + promise: existingTokenPromise ?? tokenHandle.getOrStart(), }; } else if (existingTokenPromise) { tokenReqRef.current = { @@ -407,16 +401,16 @@ export function useSigninCheck( return; } let cancelled = false; + const tokenHandle = userTokenCache.handle({ user: currentUser, forceRefresh }, () => + currentUser.getIdTokenResult(forceRefresh) + ); const tokenPromise = tokenReqRef.current && tokenReqRef.current.user === currentUser && tokenReqRef.current.forceRefresh === forceRefresh ? tokenReqRef.current.promise - : getOrStartTokenPromise(currentUser, forceRefresh); + : tokenHandle.getOrStart(); setTimeout(() => { - const byRefresh = inFlightTokenPromises.get(currentUser); - if (byRefresh?.get(forceRefresh) === tokenPromise) { - byRefresh.delete(forceRefresh); - } + tokenHandle.clear(tokenPromise); }, 0); tokenPromise.then( @@ -453,7 +447,7 @@ export function useSigninCheck( if (statusObj.status === 'success') { if (!derived) { derived = createFulfilledUsable(statusObj.data); - byKey.set(keyOrCacheKey(cacheKey), derived); + byKey.set(cacheKey, derived); } else if (derived.status === 'pending') { derived.status = 'fulfilled'; derived.value = statusObj.data; @@ -522,7 +516,12 @@ export function useSigninCheck( } if (suspenseEnabled && !hasInitialData && tokenReqRef.current) { - const idTokenResult = unwrapOrSuspend(tokenReqRef.current.promise); + const tokenHandle = userTokenCache.handle({ user: currentUser, forceRefresh }, () => + currentUser.getIdTokenResult(forceRefresh) + ); + const idTokenResult = unwrapOrSuspend(tokenReqRef.current.promise, (failed) => { + setTimeout(() => tokenHandle.clear(failed), 0); + }); return makeStatus({ status: 'success', hasEmitted: true, @@ -565,10 +564,6 @@ export function useSigninCheck( }); } -function keyOrCacheKey(key: string): string { - return key; -} - function getClaimsObjectValidator(requiredClaims: Claims): ClaimsValidator { return function claimsObjectValidator(userClaims) { const errors: { [key: string]: ReactFireError[] } = {}; diff --git a/src/database.tsx b/src/database.tsx index 3008bcfb..30b01120 100644 --- a/src/database.tsx +++ b/src/database.tsx @@ -1,12 +1,13 @@ import { - InstrumentedPromise, + InFlightRead, ObservableStatus, ReactFireOptions, checkIdField, + createInFlightCache, hasInitialDataOption, - instrumentPromise, - useMappedObservableStatus, + listenUntilReleased, useFirebaseStream, + useMappedObservableStatus, } from './useFirebaseStream.js'; import { onValue, Query as DatabaseQuery, DatabaseReference, DataSnapshot } from 'firebase/database'; @@ -30,40 +31,19 @@ export interface QueryChange { event: ListenEvent; } -function createInitialObjectPromise(ref: DatabaseReference): Promise { - return new Promise((resolve, reject) => { +function preloadObject(ref: DatabaseReference): InFlightRead { + return listenUntilReleased((onNext, onError) => onValue( ref, (snapshot) => { - resolve({ snapshot, prevKey: undefined, event: ListenEvent.value }); - }, - (err) => { - reject(err); + onNext({ snapshot, prevKey: undefined, event: ListenEvent.value }); }, - { onlyOnce: true } - ); - }); + onError + ) + ); } -const IN_FLIGHT_EVICT_MS = 30_000; -const inFlightObjectPromises = new Map>(); - -function getOrStartInFlightObject(ref: DatabaseReference): InstrumentedPromise { - const key = ref.toString(); - let promise = inFlightObjectPromises.get(key); - if (!promise) { - promise = instrumentPromise(createInitialObjectPromise(ref)); - inFlightObjectPromises.set(key, promise); - const cleanup = () => - setTimeout(() => { - if (inFlightObjectPromises.get(key) === promise) { - inFlightObjectPromises.delete(key); - } - }, IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - } - return promise; -} +const objectCache = createInFlightCache((a, b) => a.isEqual(b)); /** * Subscribe to a Realtime Database object @@ -75,18 +55,10 @@ export function useDatabaseObject( ref: DatabaseReference, options?: ReactFireOptions ): ObservableStatus { - const key = ref.toString(); - return useFirebaseStream({ ref, isEqual: (a, b) => a.isEqual(b), - peekInFlight: () => inFlightObjectPromises.get(key), - getOrStartInFlight: () => getOrStartInFlightObject(ref), - clearInFlight: (expectedPromise) => { - if (!expectedPromise || inFlightObjectPromises.get(key) === expectedPromise) { - inFlightObjectPromises.delete(key); - } - }, + inFlight: objectCache.handle(ref, () => preloadObject(ref)), subscribe: (onNext, onError) => { return onValue( ref, @@ -144,55 +116,20 @@ function snapshotToChanges(snapshot: DataSnapshot): QueryChange[] { return changes; } -function createInitialListPromise(ref: DatabaseReference | DatabaseQuery): Promise { - return new Promise((resolve, reject) => { +function preloadList(ref: DatabaseReference | DatabaseQuery): InFlightRead { + return listenUntilReleased((onNext, onError) => onValue( ref, (snapshot) => { - resolve(snapshotToChanges(snapshot)); + onNext(snapshotToChanges(snapshot)); }, - (err) => { - reject(err); - }, - { onlyOnce: true } - ); - }); -} - -const inFlightListEntries: Array<{ - ref: DatabaseReference | DatabaseQuery; - promise: InstrumentedPromise; -}> = []; - -function getOrStartInFlightList(ref: DatabaseReference | DatabaseQuery): InstrumentedPromise { - const existing = inFlightListEntries.find((entry) => entry.ref.isEqual(ref)); - if (existing) { - return existing.promise; - } - const promise = instrumentPromise(createInitialListPromise(ref)); - const entry = { ref, promise }; - inFlightListEntries.push(entry); - const cleanup = () => - setTimeout(() => { - const idx = inFlightListEntries.indexOf(entry); - if (idx > -1) inFlightListEntries.splice(idx, 1); - }, IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - return promise; -} - -function removeInFlightList( - ref: DatabaseReference | DatabaseQuery, - expectedPromise?: InstrumentedPromise -) { - const idx = inFlightListEntries.findIndex( - (entry) => entry.ref.isEqual(ref) && (!expectedPromise || entry.promise === expectedPromise) + onError + ) ); - if (idx > -1) { - inFlightListEntries.splice(idx, 1); - } } +const listCache = createInFlightCache((a, b) => a.isEqual(b)); + /** * Subscribe to a Realtime Database list * @@ -206,10 +143,7 @@ export function useDatabaseList( return useFirebaseStream({ ref, isEqual: (a, b) => a.isEqual(b), - peekInFlight: () => inFlightListEntries.find((entry) => entry.ref.isEqual(ref))?.promise, - getOrStartInFlight: () => getOrStartInFlightList(ref), - clearInFlight: (expectedPromise) => - removeInFlightList(ref, expectedPromise as InstrumentedPromise | undefined), + inFlight: listCache.handle(ref, () => preloadList(ref)), subscribe: (onNext, onError) => { return onValue( ref, diff --git a/src/firestore.tsx b/src/firestore.tsx index 69292c5d..736443be 100644 --- a/src/firestore.tsx +++ b/src/firestore.tsx @@ -1,12 +1,13 @@ import { - InstrumentedPromise, + InFlightRead, ObservableStatus, ReactFireOptions, checkIdField, + createInFlightCache, hasInitialDataOption, - instrumentPromise, - useMappedObservableStatus, + listenUntilReleased, useFirebaseStream, + useMappedObservableStatus, } from './useFirebaseStream.js'; import { @@ -18,65 +19,36 @@ import { DocumentData, DocumentSnapshot, getDocFromCache, + getDocsFromCache, onSnapshot, } from 'firebase/firestore'; -const IN_FLIGHT_EVICT_MS = 30_000; -const inFlightDocPromises = new Map>>(); +const ignore = () => undefined; -function getOrStartInFlightDoc( - key: string, - factory: () => Promise> -): InstrumentedPromise> { - let promise = inFlightDocPromises.get(key) as InstrumentedPromise> | undefined; - if (!promise) { - promise = instrumentPromise(factory()); - inFlightDocPromises.set(key, promise); - const cleanup = () => - setTimeout(() => { - if (inFlightDocPromises.get(key) === promise) { - inFlightDocPromises.delete(key); - } - }, IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - } - return promise; +function preloadDoc(ref: DocumentReference): InFlightRead> { + return listenUntilReleased>((onNext, onError) => { + const unsubscribe = onSnapshot(ref, onNext, onError); + getDocFromCache(ref).then(onNext, ignore); + return unsubscribe; + }); } -function fetchDocOnce(ref: DocumentReference): Promise> { - return getDocFromCache(ref).catch( - () => - new Promise>((resolve, reject) => { - let settled = false; - const unsubscribe = onSnapshot( - ref, - (snap) => { - if (!settled) { - settled = true; - unsubscribe?.(); - resolve(snap); - } - }, - (err) => { - if (!settled) { - settled = true; - unsubscribe?.(); - reject(err); - } - } - ); - if (settled) { - unsubscribe(); - } - }) - ); +function preloadCollection(query: FirestoreQuery): InFlightRead> { + return listenUntilReleased>((onNext, onError) => { + const unsubscribe = onSnapshot(query, onNext, onError); + getDocsFromCache(query).then((snap) => { + if (!snap.empty) { + onNext(snap); + } + }, ignore); + return unsubscribe; + }); } -function fetchCollectionOnce(query: FirestoreQuery): Promise> { - return new Promise>((resolve, reject) => { +function listenOnce(start: (onNext: (snap: S) => void, onError: (err: Error) => void) => () => void): Promise { + return new Promise((resolve, reject) => { let settled = false; - const unsubscribe = onSnapshot( - query, + const unsubscribe = start( (snap) => { if (!settled) { settled = true; @@ -98,6 +70,21 @@ function fetchCollectionOnce(query: FirestoreQuery): Promis }); } +function fetchDocOnce(ref: DocumentReference): Promise> { + return getDocFromCache(ref).catch(() => listenOnce>((onNext, onError) => onSnapshot(ref, onNext, onError))); +} + +function fetchCollectionOnce(query: FirestoreQuery): Promise> { + return listenOnce>((onNext, onError) => onSnapshot(query, onNext, onError)); +} + +// Keyed by the reference/query itself (`refEqual` / `queryEqual` compare the Firestore instance, path, and converter), +// so the same path in another database, or read through another converter, never shares an initial read. +const docCache = createInFlightCache, DocumentSnapshot>(refEqual); +const docOnceCache = createInFlightCache, DocumentSnapshot>(refEqual); +const collectionCache = createInFlightCache, QuerySnapshot>(queryEqual); +const collectionOnceCache = createInFlightCache, QuerySnapshot>(queryEqual); + /** * Preload a subscription to a Firestore document reference. * @@ -107,8 +94,7 @@ function fetchCollectionOnce(query: FirestoreQuery): Promis */ export async function preloadFirestoreDoc(refProvider: () => Promise) { const ref = await refProvider(); - const key = `firestore:doc:${ref.firestore.app.name}:${ref.path}`; - return getOrStartInFlightDoc(key, () => fetchDocOnce(ref)); + return docCache.handle(ref, () => preloadDoc(ref)).getOrStart(); } /** @@ -122,26 +108,27 @@ export function useFirestoreDoc( ): ObservableStatus> { const once = Boolean(options?.once); const includeMetadataChanges = options?.includeMetadataChanges; - const docKey = `firestore:${once ? 'docOnce' : 'doc'}:${ref.firestore.app.name}:${ref.path}`; return useFirebaseStream, DocumentSnapshot>({ ref, isEqual: refEqual, - peekInFlight: () => inFlightDocPromises.get(docKey) as InstrumentedPromise> | undefined, - getOrStartInFlight: () => getOrStartInFlightDoc(docKey, () => fetchDocOnce(ref)), - clearInFlight: (expectedPromise) => { - if (!expectedPromise || inFlightDocPromises.get(docKey) === expectedPromise) { - inFlightDocPromises.delete(docKey); - } - }, + deps: [once, includeMetadataChanges], + inFlight: (once ? docOnceCache : docCache).handle(ref, () => (once ? fetchDocOnce(ref) : preloadDoc(ref))), subscribe: (onNext, onError, inFlightPromise) => { if (once) { + let active = true; const reqPromise = inFlightPromise ?? fetchDocOnce(ref); reqPromise.then( - (snap) => onNext(snap, true), - (error: unknown) => onError(error) + (snap) => { + if (active) onNext(snap, true); + }, + (error: unknown) => { + if (active) onError(error); + } ); - return () => {}; + return () => { + active = false; + }; } let active = true; @@ -236,40 +223,6 @@ export function useFirestoreDocDataOnce( return useMappedObservableStatus(docStatus, (snap) => snapToData(snap, idField), idField ?? '', options); } -const inFlightCollectionEntries: Array<{ query: FirestoreQuery; promise: InstrumentedPromise> }> = []; - -function getOrStartInFlightCollection( - query: FirestoreQuery, - factory: () => Promise> -): InstrumentedPromise> { - const existing = inFlightCollectionEntries.find((entry) => queryEqual(entry.query, query)); - if (existing) { - return existing.promise as InstrumentedPromise>; - } - const promise = instrumentPromise(factory()); - const entry = { query, promise }; - inFlightCollectionEntries.push(entry); - const cleanup = () => - setTimeout(() => { - const idx = inFlightCollectionEntries.indexOf(entry); - if (idx > -1) inFlightCollectionEntries.splice(idx, 1); - }, IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - return promise; -} - -function removeInFlightCollection( - query: FirestoreQuery, - expectedPromise?: InstrumentedPromise> -) { - const idx = inFlightCollectionEntries.findIndex( - (entry) => queryEqual(entry.query, query) && (!expectedPromise || entry.promise === expectedPromise) - ); - if (idx > -1) { - inFlightCollectionEntries.splice(idx, 1); - } -} - /** * Subscribe to a Firestore collection */ @@ -283,20 +236,25 @@ export function useFirestoreCollection( return useFirebaseStream, QuerySnapshot>({ ref: query, isEqual: queryEqual, - peekInFlight: () => - inFlightCollectionEntries.find((entry) => queryEqual(entry.query, query))?.promise as - | InstrumentedPromise> - | undefined, - getOrStartInFlight: () => getOrStartInFlightCollection(query, () => fetchCollectionOnce(query)), - clearInFlight: (expectedPromise) => removeInFlightCollection(query, expectedPromise), + deps: [once, includeMetadataChanges], + inFlight: (once ? collectionOnceCache : collectionCache).handle(query, () => + once ? fetchCollectionOnce(query) : preloadCollection(query) + ), subscribe: (onNext, onError, inFlightPromise) => { if (once) { + let active = true; const reqPromise = inFlightPromise ?? fetchCollectionOnce(query); reqPromise.then( - (snap) => onNext(snap, true), - (error: unknown) => onError(error) + (snap) => { + if (active) onNext(snap, true); + }, + (error: unknown) => { + if (active) onError(error); + } ); - return () => {}; + return () => { + active = false; + }; } let active = true; diff --git a/src/functions.tsx b/src/functions.tsx index ad4cd73d..51aea0bf 100644 --- a/src/functions.tsx +++ b/src/functions.tsx @@ -1,29 +1,10 @@ import { httpsCallable } from 'firebase/functions'; import { useFunctions } from './sdk.js'; -import { InstrumentedPromise, ObservableStatus, ReactFireOptions, instrumentPromise, useFirebaseStream } from './useFirebaseStream.js'; +import { ObservableStatus, ReactFireOptions, createInFlightCache, useFirebaseStream } from './useFirebaseStream.js'; import type { Functions, HttpsCallableOptions } from 'firebase/functions'; -const IN_FLIGHT_EVICT_MS = 5_000; -const inFlightCallablePromises = new Map>(); - -function getOrStartCallable( - functions: Functions, - functionName: string, - callKey: string, - data?: RequestData, - httpsCallableOptions?: HttpsCallableOptions -): InstrumentedPromise { - let promise = inFlightCallablePromises.get(callKey) as InstrumentedPromise | undefined; - if (!promise) { - const callable = httpsCallable(functions, functionName, httpsCallableOptions); - promise = instrumentPromise(callable(data).then((result) => result.data)); - inFlightCallablePromises.set(callKey, promise); - const cleanup = () => setTimeout(() => inFlightCallablePromises.delete(callKey), IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - } - return promise; -} +const callableCache = createInFlightCache(); /** * Calls a callable function. @@ -45,20 +26,13 @@ export function useCallableFunctionResponse( return useFirebaseStream<{ functions: Functions; key: string }, ResponseData>({ ref: { functions, key: callKey }, isEqual: (a, b) => a.functions === b.functions && a.key === b.key, - peekInFlight: () => inFlightCallablePromises.get(callKey) as InstrumentedPromise | undefined, - getOrStartInFlight: () => - getOrStartCallable( + inFlight: callableCache.handle(callKey, () => + httpsCallable( functions, functionName, - callKey, - options?.data, options?.httpsCallableOptions - ), - clearInFlight: (expectedPromise) => { - if (!expectedPromise || inFlightCallablePromises.get(callKey) === expectedPromise) { - inFlightCallablePromises.delete(callKey); - } - }, + )(options?.data).then((result) => result.data) + ), subscribe: (onNext, onError, inFlightPromise) => { const reqPromise = inFlightPromise ?? diff --git a/src/remote-config.tsx b/src/remote-config.tsx index 2b730379..08ebbb91 100644 --- a/src/remote-config.tsx +++ b/src/remote-config.tsx @@ -1,5 +1,5 @@ import { useRemoteConfig } from './sdk.js'; -import { InstrumentedPromise, ObservableStatus, instrumentPromise, useFirebaseStream } from './useFirebaseStream.js'; +import { ObservableStatus, createInFlightCache, useFirebaseStream } from './useFirebaseStream.js'; import { ensureInitialized, getValue, @@ -21,30 +21,9 @@ export type AllParameters = { type Getter = (remoteConfig: RemoteConfig, key: string) => T; const initializedRemoteConfigs = new WeakSet(); -const inFlightRemoteConfigValues = new WeakMap>>(); - -function getOrStartRemoteConfigValue( - remoteConfig: RemoteConfig, - key: string, - getter: Getter -): InstrumentedPromise { - let byKey = inFlightRemoteConfigValues.get(remoteConfig); - if (!byKey) { - byKey = new Map(); - inFlightRemoteConfigValues.set(remoteConfig, byKey); - } - let promise = byKey.get(key) as InstrumentedPromise | undefined; - if (!promise) { - promise = instrumentPromise( - ensureInitialized(remoteConfig).then(() => { - initializedRemoteConfigs.add(remoteConfig); - return getter(remoteConfig, key); - }) - ); - byKey.set(key, promise); - } - return promise; -} +const remoteConfigCache = createInFlightCache<{ remoteConfig: RemoteConfig; key: string }, any>( + (a, b) => a.remoteConfig === b.remoteConfig && a.key === b.key +); /** * Helper function to construct type safe functions. Since Remote Config has @@ -56,18 +35,17 @@ function getOrStartRemoteConfigValue( */ function useRemoteConfigValue_INTERNAL(key: string, getter: Getter): ObservableStatus { const remoteConfig = useRemoteConfig(); + const ref = { remoteConfig, key }; return useFirebaseStream<{ remoteConfig: RemoteConfig; key: string }, T>({ - ref: { remoteConfig, key }, + ref, isEqual: (a, b) => a.remoteConfig === b.remoteConfig && a.key === b.key, - peekInFlight: () => inFlightRemoteConfigValues.get(remoteConfig)?.get(key), - getOrStartInFlight: () => getOrStartRemoteConfigValue(remoteConfig, key, getter), - clearInFlight: (expectedPromise) => { - const byKey = inFlightRemoteConfigValues.get(remoteConfig); - if (byKey && (!expectedPromise || byKey.get(key) === expectedPromise)) { - byKey.delete(key); - } - }, + inFlight: remoteConfigCache.handle(ref, () => + ensureInitialized(remoteConfig).then(() => { + initializedRemoteConfigs.add(remoteConfig); + return getter(remoteConfig, key); + }) + ), getSyncInitialValue: () => initializedRemoteConfigs.has(remoteConfig) ? { hasValue: true, value: getter(remoteConfig, key), isComplete: true } diff --git a/src/sdk.tsx b/src/sdk.tsx index 19301dcb..a5ec1932 100644 --- a/src/sdk.tsx +++ b/src/sdk.tsx @@ -11,7 +11,7 @@ import type { FirebaseStorage } from 'firebase/storage'; import type { RemoteConfig } from 'firebase/remote-config'; import { useFirebaseApp } from './firebaseApp.js'; import { FirebaseApp } from 'firebase/app'; -import { InstrumentedPromise, ObservableStatus, ReactFireOptions, instrumentPromise, useFirebaseStream } from './useFirebaseStream.js'; +import { ObservableStatus, ReactFireOptions, createInFlightCache, useFirebaseStream } from './useFirebaseStream.js'; /** @deprecated Pass Firebase SDK instances or references directly instead of using Context Providers. Will be removed in v5.0.0. */ export const AppCheckSdkContext = React.createContext(undefined); @@ -56,7 +56,7 @@ function useSdk(SdkContext: React.Context>(); +const initSdkCache = createInFlightCache(); function useInitSdk( sdkName: string, @@ -74,26 +74,13 @@ function useInitSdk( } const key = `firebase-sdk:${sdkName}:${firebaseApp.name}`; - const getOrStart = () => { - let p = inFlightInitSdk.get(key) as InstrumentedPromise | undefined; - if (!p) { - p = instrumentPromise(sdkInitializer(firebaseApp)); - inFlightInitSdk.set(key, p); - } - return p; - }; + const inFlight = initSdkCache.handle(key, () => sdkInitializer(firebaseApp)); return useFirebaseStream({ ref: key, - peekInFlight: () => inFlightInitSdk.get(key) as InstrumentedPromise | undefined, - getOrStartInFlight: getOrStart, - clearInFlight: (expected) => { - if (!expected || inFlightInitSdk.get(key) === expected) { - inFlightInitSdk.delete(key); - } - }, + inFlight, subscribe: (onNext, onError, inFlightPromise) => { - const p = inFlightPromise ?? getOrStart(); + const p = inFlightPromise ?? inFlight.getOrStart(); p.then( (sdk) => onNext(sdk, true), (err) => onError(err) diff --git a/src/storage.tsx b/src/storage.tsx index a225ea71..90d9795c 100644 --- a/src/storage.tsx +++ b/src/storage.tsx @@ -1,56 +1,46 @@ import * as React from 'react'; import { useStorage } from './sdk.js'; import { useSuspenseEnabledFromConfigAndContext } from './firebaseApp.js'; -import { InstrumentedPromise, ObservableStatus, ReactFireOptions, instrumentPromise, useFirebaseStream } from './useFirebaseStream.js'; +import { ObservableStatus, ReactFireOptions, createInFlightCache, useFirebaseStream } from './useFirebaseStream.js'; import { getDownloadURL, ref } from 'firebase/storage'; import type { UploadTask, UploadTaskSnapshot, StorageReference, FirebaseStorage } from 'firebase/storage'; -const IN_FLIGHT_EVICT_MS = 5_000; -const inFlightStorageTasks = new WeakMap>(); - -function getOrStartStorageTask(task: UploadTask): InstrumentedPromise { - let promise = inFlightStorageTasks.get(task); - if (!promise) { - promise = instrumentPromise( - new Promise((resolve, reject) => { - let settled = false; - const timer = setTimeout(() => { - if (!settled) { - settled = true; - unsub?.(); - resolve(task.snapshot); - } - }, 0); - const unsub = task.on( - 'state_changed', - (snap) => { - if (!settled) { - settled = true; - clearTimeout(timer); - unsub?.(); - resolve(snap); - } - }, - (err) => { - if (!settled) { - settled = true; - clearTimeout(timer); - unsub?.(); - reject(err); - } - } - ); - if (settled) { - unsub(); +const storageTaskCache = createInFlightCache(); + +function fetchStorageTaskOnce(task: UploadTask): Promise { + return new Promise((resolve, reject) => { + let settled = false; + const timer = setTimeout(() => { + if (!settled) { + settled = true; + unsub?.(); + resolve(task.snapshot); + } + }, 0); + const unsub = task.on( + 'state_changed', + (snap) => { + if (!settled) { + settled = true; + clearTimeout(timer); + unsub?.(); + resolve(snap); + } + }, + (err) => { + if (!settled) { + settled = true; + clearTimeout(timer); + unsub?.(); + reject(err); } - }) + } ); - inFlightStorageTasks.set(task, promise); - const cleanup = () => setTimeout(() => inFlightStorageTasks.delete(task), IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - } - return promise; + if (settled) { + unsub(); + } + }); } /** @@ -71,9 +61,7 @@ export function useStorageTask( return useFirebaseStream<{ task: UploadTask; key: string }, UploadTaskSnapshot | T>({ ref: { task, key: taskKey }, isEqual: (a, b) => a.task === b.task && a.key === b.key, - peekInFlight: () => inFlightStorageTasks.get(task), - getOrStartInFlight: () => getOrStartStorageTask(task), - clearInFlight: () => inFlightStorageTasks.delete(task), + inFlight: storageTaskCache.handle(task, () => fetchStorageTaskOnce(task)), subscribe: (onNext, onError) => { let emitted = false; let completed = false; @@ -112,23 +100,12 @@ export function useStorageTask( }); } -const inFlightDownloadUrls = new Map>(); - -function getOrStartDownloadUrl(key: string, storageRef: StorageReference): InstrumentedPromise { - let promise = inFlightDownloadUrls.get(key); - if (!promise) { - promise = instrumentPromise(getDownloadURL(storageRef)); - inFlightDownloadUrls.set(key, promise); - const cleanup = () => setTimeout(() => inFlightDownloadUrls.delete(key), IN_FLIGHT_EVICT_MS); - promise.then(cleanup, cleanup); - } - return promise; -} +const downloadUrlCache = createInFlightCache(); /** * Subscribe to a storage ref's download URL * - * @param ref - reference to the blob you want to download + * @param storageRef - reference to the blob you want to download * @param options */ export function useStorageDownloadURL( @@ -139,13 +116,7 @@ export function useStorageDownloadURL( return useFirebaseStream({ ref: urlKey, - peekInFlight: () => inFlightDownloadUrls.get(urlKey), - getOrStartInFlight: () => getOrStartDownloadUrl(urlKey, storageRef), - clearInFlight: (expectedPromise) => { - if (!expectedPromise || inFlightDownloadUrls.get(urlKey) === expectedPromise) { - inFlightDownloadUrls.delete(urlKey); - } - }, + inFlight: downloadUrlCache.handle(urlKey, () => getDownloadURL(storageRef)), subscribe: (onNext, onError, inFlightPromise) => { const reqPromise = (inFlightPromise as Promise | undefined) ?? getDownloadURL(storageRef); reqPromise.then( diff --git a/src/useFirebaseStream.ts b/src/useFirebaseStream.ts index cb0b9edc..fa308326 100644 --- a/src/useFirebaseStream.ts +++ b/src/useFirebaseStream.ts @@ -2,6 +2,21 @@ import * as React from 'react'; import { registerVersion } from 'firebase/app'; import { useSuspenseEnabledFromConfigAndContext, version } from './firebaseApp.js'; +/** + * Report the React and ReactFire versions to the Firebase SDK so they show up + * in Firebase usage telemetry. `registerVersion` is idempotent and order-independent inside + * `@firebase/app`, so it is safe to call before or after `initializeApp`; we still only call it once. + */ +let versionRegistered = false; +export function registerReactFireVersion(): void { + if (versionRegistered) { + return; + } + versionRegistered = true; + registerVersion('react', React.version || 'unknown'); + registerVersion('reactfire', version || 'unknown'); +} + /** * A thenable compatible with React 19's `React.use()` while remaining structurally valid * under `@types/react` 16, 17, 18, and 19. @@ -163,11 +178,12 @@ export function createFulfilledUsable(value: T): InstrumentedPromise { return p; } -export function unwrapOrSuspend(promise: InstrumentedPromise): T { +export function unwrapOrSuspend(promise: InstrumentedPromise, onRejected?: (failed: InstrumentedPromise) => void): T { if (promise.status === 'fulfilled') { return promise.value as T; } if (promise.status === 'rejected') { + onRejected?.(promise); throw promise.reason; } throw promise; @@ -192,6 +208,148 @@ export function attachUsable( return target as ObservableStatus; } +/** + * The initial read for a single stream key. Shared by every component rendering the same + * reference so that concurrent renders, `` double-mounts, and Suspense retries all see the + * exact same promise instance. + */ +export interface InFlightHandle { + peek(): InstrumentedPromise | undefined; + getOrStart(): InstrumentedPromise; + /** Called once a committed component's live subscription is attached; hands the read off to it. */ + clear(promise?: InstrumentedPromise): void; +} + +/** + * An initial read that may keep a resource warm after it resolves, e.g. the listener that produced the first + * snapshot. `release` runs once the live subscription has taken over (or the read is abandoned), so the + * listener stays attached until then and the SDK never drops and re-downloads the data in between. + */ +export interface InFlightRead { + promise: Promise; + release?: () => void; +} + +export interface InFlightCache { + handle(key: K, start: () => Promise | InFlightRead): InFlightHandle; + /** Drop every entry so the next render starts fresh reads. */ + clearAll(): void; +} + +interface InFlightEntry { + key: K; + promise: InstrumentedPromise; + release: () => void; +} + +const allInFlightCaches = new Set>(); + +/** + * Forget every in-flight initial read, across all caches. Called by the Auth hooks when the signed-in user changes, + * so a read that was started for the previous user (and never committed) can't be served to the next render. + */ +export function clearAllInFlight(): void { + allInFlightCaches.forEach((cache) => cache.clearAll()); +} + +function isPromise(value: Promise | InFlightRead): value is Promise { + return typeof (value as Promise).then === 'function'; +} + +/** + * Creates a module-level cache of in-flight initial reads keyed by `K`. + * + * Entries are handed off (and their `release` called) when a committed component's live subscription has attached, + * or evicted `evictAfterMs` after settling if no component ever commits (an abandoned render). + */ +export function createInFlightCache(isEqual: (a: K, b: K) => boolean = Object.is, evictAfterMs = 300): InFlightCache { + const entries: Array> = []; + const find = (key: K) => entries.find((entry) => isEqual(entry.key, key)); + const detach = (entry: InFlightEntry) => { + const idx = entries.indexOf(entry); + if (idx > -1) { + entries.splice(idx, 1); + } + // Never cut a pending read short: a component may still be suspended on it, and React only retries once it settles. + if (entry.promise.status === 'pending') { + entry.promise.then(entry.release, entry.release); + } else { + entry.release(); + } + }; + + const cache: InFlightCache = { + handle(key, start) { + return { + peek: () => find(key)?.promise, + getOrStart: () => { + const existing = find(key); + if (existing) { + return existing.promise; + } + const started = start(); + const read = isPromise(started) ? { promise: started } : started; + let released = false; + const entry: InFlightEntry = { + key, + promise: instrumentPromise(read.promise), + release: () => { + if (!released) { + released = true; + read.release?.(); + } + }, + }; + entries.push(entry); + const evict = () => + setTimeout(() => { + if (entries.includes(entry)) { + detach(entry); + } + }, evictAfterMs); + entry.promise.then(evict, evict); + return entry.promise; + }, + clear: (promise) => { + const existing = find(key); + // A still-pending read may have other components suspended on it (e.g. a non-Suspense sibling committed + // first); leave it for them and let eviction hand it off once it settles. + if (existing && (!promise || existing.promise === promise) && existing.promise.status !== 'pending') { + detach(existing); + } + }, + }; + }, + clearAll: () => { + [...entries].forEach(detach); + }, + }; + allInFlightCaches.add(cache as InFlightCache); + return cache; +} + +/** + * Starts a listener whose first emission resolves the returned promise, and keeps it attached until `release` is + * called. Firestore and Realtime Database drop cached data as soon as the last listener detaches, so detaching the + * preload listener before the component's live listener attaches would download everything twice. + */ +export function listenUntilReleased(listen: (onNext: (value: S) => void, onError: (error: Error) => void) => () => void): InFlightRead { + let unsubscribe: (() => void) | undefined; + let released = false; + const promise = new Promise((resolve, reject) => { + unsubscribe = listen(resolve, reject); + }); + return { + promise, + release: () => { + if (!released) { + released = true; + unsubscribe?.(); + } + }, + }; +} + export function hasInitialDataOption(options?: ReactFireOptions): boolean { return Boolean(options && ('initialData' in options || 'startWithValue' in options)); } @@ -323,9 +481,9 @@ export function useMappedObservableStatus( export interface FirebaseStreamConfig { ref: Ref; isEqual?: (a: Ref, b: Ref) => boolean; - peekInFlight: () => InstrumentedPromise | undefined; - getOrStartInFlight: () => InstrumentedPromise; - clearInFlight: (promise?: InstrumentedPromise) => void; + /** Options that change what `subscribe` does (e.g. `once`, `includeMetadataChanges`); changing one re-subscribes. */ + deps?: React.DependencyList; + inFlight: InFlightHandle; subscribe: ( onNext: (value: T, isComplete?: boolean) => void, onError: (error: unknown) => void, @@ -337,15 +495,13 @@ export interface FirebaseStreamConfig { } export function useFirebaseStream(config: FirebaseStreamConfig): ObservableStatus { - registerVersion('react', React.version || 'unknown'); - registerVersion('reactfire', version); + registerReactFireVersion(); const { ref, isEqual = Object.is, - peekInFlight, - getOrStartInFlight, - clearInFlight, + deps = [], + inFlight, subscribe, options, getSyncInitialValue, @@ -366,9 +522,9 @@ export function useFirebaseStream(config: FirebaseStreamConfig): const reqRef = React.useRef<{ ref: Ref; promise: InstrumentedPromise } | null>(null); if (!hasInitialData) { const existingForRef = - reqRef.current && isEqual(reqRef.current.ref, ref) ? reqRef.current.promise : peekInFlight(); + reqRef.current && isEqual(reqRef.current.ref, ref) ? reqRef.current.promise : inFlight.peek(); if (suspenseEnabled && (!syncInit?.hasValue || existingForRef)) { - reqRef.current = { ref, promise: existingForRef ?? getOrStartInFlight() }; + reqRef.current = { ref, promise: existingForRef ?? inFlight.getOrStart() }; } else if (!syncInit?.hasValue && existingForRef) { reqRef.current = { ref, promise: existingForRef }; } else { @@ -382,7 +538,9 @@ export function useFirebaseStream(config: FirebaseStreamConfig): const preloadedValue = suspenseEnabled && !hasInitialData && reqPromise - ? unwrapOrSuspend(reqPromise) + ? unwrapOrSuspend(reqPromise, (failed) => { + setTimeout(() => inFlight.clear(failed), 0); + }) : reqPromise?.status === 'fulfilled' ? (reqPromise.value as T) : undefined; @@ -450,10 +608,6 @@ export function useFirebaseStream(config: FirebaseStreamConfig): ); } - setTimeout(() => { - clearInFlight(currentPromise); - }, 0); - const unsubscribe = subscribe( (nextValue, isComplete = defaultIsComplete) => { if (active) { @@ -484,11 +638,19 @@ export function useFirebaseStream(config: FirebaseStreamConfig): currentPromise ); + if (currentPromise) { + setTimeout(() => { + inFlight.clear(currentPromise); + }, 0); + } + return () => { active = false; unsubscribe(); }; - }, [canonicalRef]); + // `canonicalRef` only changes when `isEqual` says the reference changed; `deps` are the options `subscribe` reads. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [canonicalRef, ...deps]); const isCurrent = isEqual(state.ref, ref); const hasStreamValue = (isCurrent && state.hasValue) || Boolean(syncInit?.hasValue) || preloadedReady; @@ -516,6 +678,12 @@ export function useFirebaseStream(config: FirebaseStreamConfig): } const getUsable = (): ReactFireUsable => { + if (reqPromise?.status === 'rejected') { + const failed = reqPromise; + setTimeout(() => inFlight.clear(failed), 0); + return failed; + } + if (hasResolved) { if ( !usableRef.current || @@ -532,7 +700,7 @@ export function useFirebaseStream(config: FirebaseStreamConfig): } if (!reqRef.current || !isEqual(reqRef.current.ref, ref)) { - reqRef.current = { ref, promise: getOrStartInFlight() }; + reqRef.current = { ref, promise: inFlight.getOrStart() }; } usableRef.current = reqRef.current; return reqRef.current.promise; diff --git a/test/firestore.test.tsx b/test/firestore.test.tsx index 6558f872..cd8eb968 100644 --- a/test/firestore.test.tsx +++ b/test/firestore.test.tsx @@ -17,7 +17,7 @@ import { baseConfig } from './appConfig'; import { randomString } from './test-utils'; import { addDoc, collection, doc, getFirestore, query, setDoc, connectFirestoreEmulator, where } from 'firebase/firestore'; -import type { DocumentReference } from 'firebase/firestore'; +import type { DocumentReference, FirestoreDataConverter } from 'firebase/firestore'; describe('Firestore', () => { const app = initializeApp(baseConfig, 'firestore-test-suite'); @@ -522,5 +522,38 @@ describe('Firestore', () => { expect(docResult.current.data.data()).toEqual({ a: 'first' }); expect(colResult.current.data.docs).toHaveLength(1); }); + + it('separates in-flight Suspense cache entries for the same document path with and without a converter', async () => { + const colRef = collection(db, randomString()); + const rawRef = doc(colRef, 'shared'); + await setDoc(rawRef, { v: 7 }); + + const converter: FirestoreDataConverter<{ converted: boolean; v: number }> = { + toFirestore: (d) => ({ v: d.v }), + fromFirestore: (snap) => ({ converted: true, v: (snap.data() as { v: number }).v }), + }; + const convertedRef = rawRef.withConverter(converter); + + const RawDoc = () => { + const { data } = useFirestoreDocData(rawRef, { suspense: true }); + return
{JSON.stringify(data)}
; + }; + const ConvertedDoc = () => { + const { data } = useFirestoreDocData(convertedRef, { suspense: true }); + return
{JSON.stringify(data)}
; + }; + + const { getByTestId } = render( + + loading}> + + + + + ); + + await waitFor(() => expect(getByTestId('conv')).toBeInTheDocument()); + expect(JSON.parse(getByTestId('conv').textContent || '{}').converted).toBe(true); + }); }); }); diff --git a/test/useObservable.test.tsx b/test/useObservable.test.tsx index ada53ab5..39abbd2b 100644 --- a/test/useObservable.test.tsx +++ b/test/useObservable.test.tsx @@ -427,4 +427,34 @@ describe('useObservable', () => { expect(html).not.toContain('another-requests-value'); }); }); + + describe('Subscription lifecycle', () => { + it('releases the underlying source subscription after unmount', async () => { + for (const suspense of [false, true]) { + const subject$ = new Subject(); + const observableId = `sub-release-${suspense}-${Math.random()}`; + + const Obs = () => { + const { data } = useObservable(observableId, subject$, { suspense }); + return
{String(data)}
; + }; + + const { unmount, getByTestId } = render( + loading}> + + + ); + + await act(async () => { + subject$.next(42); + }); + + await waitFor(() => expect(getByTestId('obs')).toHaveTextContent('42')); + expect(subject$.observed).toBe(true); + + unmount(); + expect(subject$.observed).toBe(false); + } + }); + }); }); diff --git a/tsconfig.json b/tsconfig.json index 7cb9a9dd..6a04c649 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs "include": ["src", "types"], "compilerOptions": { - "target": "es2015", + "target": "es2020", "module": "esnext", "lib": ["dom", "esnext"], "types": ["vitest/globals"], diff --git a/vite.config.ts b/vite.config.ts index 90543452..1bcc4471 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -69,9 +69,12 @@ export default defineConfig({ }, rollupOptions: { external: externals, - output: { - globals: { react: 'React' }, - }, + output: [ + // One ES module per source file, so `"sideEffects": false` lets bundlers drop the products an app doesn't use + // (and their `firebase/*` imports) instead of pulling every Firebase SDK in through a single index.js. + { format: 'es', preserveModules: true, preserveModulesRoot: 'src', entryFileNames: '[name].js' }, + { format: 'umd', name: 'ReactFire', entryFileNames: 'index.umd.cjs', globals: { react: 'React' } }, + ], }, sourcemap: true, },