diff --git a/docs/reference/README.md b/docs/reference/README.md
index 7606dba0..f64f5ce0 100644
--- a/docs/reference/README.md
+++ b/docs/reference/README.md
@@ -14,7 +14,10 @@ ReactFire reference docs
- [ClaimCheckErrors](interfaces/ClaimCheckErrors.md)
- [ClaimsCheckProps](interfaces/ClaimsCheckProps.md)
- [ClaimsValidator](interfaces/ClaimsValidator.md)
-- [ObservableStatus](interfaces/ObservableStatus.md)
+- [FirebaseAppProviderProps](interfaces/FirebaseAppProviderProps.md)
+- [ObservableStatusError](interfaces/ObservableStatusError.md)
+- [ObservableStatusLoading](interfaces/ObservableStatusLoading.md)
+- [ObservableStatusSuccess](interfaces/ObservableStatusSuccess.md)
- [ReactFireOptions](interfaces/ReactFireOptions.md)
- [SignInCheckOptionsBasic](interfaces/SignInCheckOptionsBasic.md)
- [SignInCheckOptionsClaimsObject](interfaces/SignInCheckOptionsClaimsObject.md)
@@ -23,8 +26,10 @@ ReactFire reference docs
### Type Aliases
+- [ObservableStatus](README.md#observablestatus)
- [ReactFireGlobals](README.md#reactfireglobals)
- [SigninCheckResult](README.md#signincheckresult)
+- [StorageImageProps](README.md#storageimageprops)
### Variables
@@ -107,6 +112,22 @@ ReactFire reference docs
## Type Aliases
+### ObservableStatus
+
+Ƭ **ObservableStatus**<`T`\>: [`ObservableStatusLoading`](interfaces/ObservableStatusLoading.md)<`T`\> \| [`ObservableStatusError`](interfaces/ObservableStatusError.md)<`T`\> \| [`ObservableStatusSuccess`](interfaces/ObservableStatusSuccess.md)<`T`\>
+
+#### Type parameters
+
+| Name |
+| :------ |
+| `T` |
+
+#### Defined in
+
+[src/useObservable.ts:84](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L84)
+
+___
+
### ReactFireGlobals
Ƭ **ReactFireGlobals**: `Object`
@@ -133,6 +154,25 @@ ___
[src/auth.tsx:59](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L59)
+___
+
+### StorageImageProps
+
+Ƭ **StorageImageProps**: `Object`
+
+#### Type declaration
+
+| Name | Type |
+| :------ | :------ |
+| `placeHolder?` | `JSX.Element` |
+| `storage?` | `FirebaseStorage` |
+| `storagePath` | `string` |
+| `suspense?` | `boolean` |
+
+#### Defined in
+
+[src/storage.tsx:36](https://github.com/FirebaseExtended/reactfire/blob/main/src/storage.tsx#L36)
+
## Variables
### AnalyticsSdkContext
@@ -299,7 +339,7 @@ Meant for Concurrent mode only (``). [More
#### Defined in
-[src/auth.tsx:240](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L240)
+[src/auth.tsx:247](https://github.com/FirebaseExtended/reactfire/blob/main/src/auth.tsx#L247)
___
@@ -379,7 +419,7 @@ ___
| Name | Type |
| :------ | :------ |
-| `props` | `PropsWithChildren`<`FirebaseAppProviderProps`\> |
+| `props` | `PropsWithChildren`<[`FirebaseAppProviderProps`](interfaces/FirebaseAppProviderProps.md)\> |
#### Returns
@@ -479,7 +519,7 @@ ___
| Name | Type |
| :------ | :------ |
-| `props` | `StorageImageProps` & `ClassAttributes`<`HTMLImageElement`\> & `ImgHTMLAttributes`<`HTMLImageElement`\> |
+| `props` | [`StorageImageProps`](README.md#storageimageprops) & `ClassAttributes`<`HTMLImageElement`\> & `ImgHTMLAttributes`<`HTMLImageElement`\> |
#### Returns
@@ -640,7 +680,7 @@ ___
#### Defined in
-[src/useObservable.ts:19](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L19)
+[src/useObservable.ts:20](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L20)
___
@@ -708,7 +748,7 @@ ___
### useCallableFunctionResponse
-▸ **useCallableFunctionResponse**<`RequestData`, `ResponseData`\>(`functionName`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`ResponseData`\>
+▸ **useCallableFunctionResponse**<`RequestData`, `ResponseData`\>(`functionName`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`ResponseData`\>
Calls a callable function.
@@ -728,7 +768,7 @@ Calls a callable function.
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`ResponseData`\>
+[`ObservableStatus`](README.md#observablestatus)<`ResponseData`\>
#### Defined in
@@ -752,7 +792,7 @@ ___
### useDatabaseList
-▸ **useDatabaseList**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`QueryChange`[] \| `T`[]\>
+▸ **useDatabaseList**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`QueryChange`[] \| `T`[]\>
Subscribe to a Realtime Database list
@@ -771,7 +811,7 @@ Subscribe to a Realtime Database list
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`QueryChange`[] \| `T`[]\>
+[`ObservableStatus`](README.md#observablestatus)<`QueryChange`[] \| `T`[]\>
#### Defined in
@@ -781,7 +821,7 @@ ___
### useDatabaseListData
-▸ **useDatabaseListData**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`T`[] \| ``null``\>
+▸ **useDatabaseListData**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`T`[] \| ``null``\>
#### Type parameters
@@ -798,7 +838,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`T`[] \| ``null``\>
+[`ObservableStatus`](README.md#observablestatus)<`T`[] \| ``null``\>
#### Defined in
@@ -808,7 +848,7 @@ ___
### useDatabaseObject
-▸ **useDatabaseObject**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`QueryChange` \| `T`\>
+▸ **useDatabaseObject**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`QueryChange` \| `T`\>
Subscribe to a Realtime Database object
@@ -827,7 +867,7 @@ Subscribe to a Realtime Database object
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`QueryChange` \| `T`\>
+[`ObservableStatus`](README.md#observablestatus)<`QueryChange` \| `T`\>
#### Defined in
@@ -837,7 +877,7 @@ ___
### useDatabaseObjectData
-▸ **useDatabaseObjectData**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`T`\>
+▸ **useDatabaseObjectData**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`T`\>
#### Type parameters
@@ -854,7 +894,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`T`\>
+[`ObservableStatus`](README.md#observablestatus)<`T`\>
#### Defined in
@@ -892,7 +932,7 @@ ___
### useFirestoreCollection
-▸ **useFirestoreCollection**<`T`\>(`query`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`QuerySnapshot`<`T`\>\>
+▸ **useFirestoreCollection**<`T`\>(`query`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`QuerySnapshot`<`T`\>\>
Subscribe to a Firestore collection
@@ -911,7 +951,7 @@ Subscribe to a Firestore collection
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`QuerySnapshot`<`T`\>\>
+[`ObservableStatus`](README.md#observablestatus)<`QuerySnapshot`<`T`\>\>
#### Defined in
@@ -921,7 +961,7 @@ ___
### useFirestoreCollectionData
-▸ **useFirestoreCollectionData**<`T`\>(`query`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`T`[]\>
+▸ **useFirestoreCollectionData**<`T`\>(`query`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`T`[]\>
Subscribe to a Firestore collection and unwrap the snapshot into an array.
@@ -940,7 +980,7 @@ Subscribe to a Firestore collection and unwrap the snapshot into an array.
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`T`[]\>
+[`ObservableStatus`](README.md#observablestatus)<`T`[]\>
#### Defined in
@@ -950,7 +990,7 @@ ___
### useFirestoreDoc
-▸ **useFirestoreDoc**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`DocumentSnapshot`<`T`\>\>
+▸ **useFirestoreDoc**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`DocumentSnapshot`<`T`\>\>
Subscribe to Firestore Document changes
@@ -971,7 +1011,7 @@ You can preload data for this hook by calling `preloadFirestoreDoc`
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`DocumentSnapshot`<`T`\>\>
+[`ObservableStatus`](README.md#observablestatus)<`DocumentSnapshot`<`T`\>\>
#### Defined in
@@ -981,7 +1021,7 @@ ___
### useFirestoreDocData
-▸ **useFirestoreDocData**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`T`\>
+▸ **useFirestoreDocData**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`T`\>
Subscribe to Firestore Document changes and unwrap the document into a plain object
@@ -1000,7 +1040,7 @@ Subscribe to Firestore Document changes and unwrap the document into a plain obj
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`T`\>
+[`ObservableStatus`](README.md#observablestatus)<`T`\>
#### Defined in
@@ -1010,7 +1050,7 @@ ___
### useFirestoreDocDataOnce
-▸ **useFirestoreDocDataOnce**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`T`\>
+▸ **useFirestoreDocDataOnce**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`T`\>
Get a Firestore document, unwrap the document into a plain object, and don't subscribe to changes
@@ -1029,7 +1069,7 @@ Get a Firestore document, unwrap the document into a plain object, and don't sub
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`T`\>
+[`ObservableStatus`](README.md#observablestatus)<`T`\>
#### Defined in
@@ -1039,7 +1079,7 @@ ___
### useFirestoreDocOnce
-▸ **useFirestoreDocOnce**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`DocumentSnapshot`<`T`\>\>
+▸ **useFirestoreDocOnce**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`DocumentSnapshot`<`T`\>\>
Get a firestore document and don't subscribe to changes
@@ -1058,7 +1098,7 @@ Get a firestore document and don't subscribe to changes
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`DocumentSnapshot`<`T`\>\>
+[`ObservableStatus`](README.md#observablestatus)<`DocumentSnapshot`<`T`\>\>
#### Defined in
@@ -1082,7 +1122,7 @@ ___
### useIdTokenResult
-▸ **useIdTokenResult**(`user`, `forceRefresh?`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`IdTokenResult`\>
+▸ **useIdTokenResult**(`user`, `forceRefresh?`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`IdTokenResult`\>
#### Parameters
@@ -1094,7 +1134,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`IdTokenResult`\>
+[`ObservableStatus`](README.md#observablestatus)<`IdTokenResult`\>
#### Defined in
@@ -1104,7 +1144,7 @@ ___
### useInitAnalytics
-▸ **useInitAnalytics**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`Analytics`\>
+▸ **useInitAnalytics**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`Analytics`\>
#### Parameters
@@ -1115,7 +1155,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`Analytics`\>
+[`ObservableStatus`](README.md#observablestatus)<`Analytics`\>
#### Defined in
@@ -1125,7 +1165,7 @@ ___
### useInitAppCheck
-▸ **useInitAppCheck**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`AppCheck`\>
+▸ **useInitAppCheck**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`AppCheck`\>
#### Parameters
@@ -1136,7 +1176,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`AppCheck`\>
+[`ObservableStatus`](README.md#observablestatus)<`AppCheck`\>
#### Defined in
@@ -1146,7 +1186,7 @@ ___
### useInitAuth
-▸ **useInitAuth**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`Auth`\>
+▸ **useInitAuth**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`Auth`\>
#### Parameters
@@ -1157,7 +1197,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`Auth`\>
+[`ObservableStatus`](README.md#observablestatus)<`Auth`\>
#### Defined in
@@ -1167,7 +1207,7 @@ ___
### useInitDatabase
-▸ **useInitDatabase**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`Database`\>
+▸ **useInitDatabase**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`Database`\>
#### Parameters
@@ -1178,7 +1218,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`Database`\>
+[`ObservableStatus`](README.md#observablestatus)<`Database`\>
#### Defined in
@@ -1188,7 +1228,7 @@ ___
### useInitFirestore
-▸ **useInitFirestore**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`Firestore`\>
+▸ **useInitFirestore**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`Firestore`\>
#### Parameters
@@ -1199,7 +1239,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`Firestore`\>
+[`ObservableStatus`](README.md#observablestatus)<`Firestore`\>
#### Defined in
@@ -1209,7 +1249,7 @@ ___
### useInitFunctions
-▸ **useInitFunctions**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`Functions`\>
+▸ **useInitFunctions**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`Functions`\>
#### Parameters
@@ -1220,7 +1260,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`Functions`\>
+[`ObservableStatus`](README.md#observablestatus)<`Functions`\>
#### Defined in
@@ -1230,7 +1270,7 @@ ___
### useInitPerformance
-▸ **useInitPerformance**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`FirebasePerformance`\>
+▸ **useInitPerformance**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`FirebasePerformance`\>
#### Parameters
@@ -1241,7 +1281,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`FirebasePerformance`\>
+[`ObservableStatus`](README.md#observablestatus)<`FirebasePerformance`\>
#### Defined in
@@ -1251,7 +1291,7 @@ ___
### useInitRemoteConfig
-▸ **useInitRemoteConfig**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`RemoteConfig`\>
+▸ **useInitRemoteConfig**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`RemoteConfig`\>
#### Parameters
@@ -1262,7 +1302,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`RemoteConfig`\>
+[`ObservableStatus`](README.md#observablestatus)<`RemoteConfig`\>
#### Defined in
@@ -1272,7 +1312,7 @@ ___
### useInitStorage
-▸ **useInitStorage**(`initializer`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`FirebaseStorage`\>
+▸ **useInitStorage**(`initializer`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`FirebaseStorage`\>
#### Parameters
@@ -1283,7 +1323,7 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`FirebaseStorage`\>
+[`ObservableStatus`](README.md#observablestatus)<`FirebaseStorage`\>
#### Defined in
@@ -1307,7 +1347,7 @@ ___
### useObservable
-▸ **useObservable**<`T`\>(`observableId`, `source`, `config?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`T`\>
+▸ **useObservable**<`T`\>(`observableId`, `source`, `config?`): [`ObservableStatus`](README.md#observablestatus)<`T`\>
#### Type parameters
@@ -1325,11 +1365,11 @@ ___
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`T`\>
+[`ObservableStatus`](README.md#observablestatus)<`T`\>
#### Defined in
-[src/useObservable.ts:95](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L95)
+[src/useObservable.ts:86](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L86)
___
@@ -1363,7 +1403,7 @@ ___
### useRemoteConfigAll
-▸ **useRemoteConfigAll**(`key`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`AllParameters`\>
+▸ **useRemoteConfigAll**(`key`): [`ObservableStatus`](README.md#observablestatus)<`AllParameters`\>
Convience method similar to useRemoteConfigValue. Returns allRemote Config parameters.
@@ -1375,7 +1415,7 @@ Convience method similar to useRemoteConfigValue. Returns allRemote Config param
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`AllParameters`\>
+[`ObservableStatus`](README.md#observablestatus)<`AllParameters`\>
#### Defined in
@@ -1385,7 +1425,7 @@ ___
### useRemoteConfigBoolean
-▸ **useRemoteConfigBoolean**(`key`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`boolean`\>
+▸ **useRemoteConfigBoolean**(`key`): [`ObservableStatus`](README.md#observablestatus)<`boolean`\>
Convience method similar to useRemoteConfigValue. Returns a `boolean` from a Remote Config parameter.
@@ -1397,7 +1437,7 @@ Convience method similar to useRemoteConfigValue. Returns a `boolean` from a Rem
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`boolean`\>
+[`ObservableStatus`](README.md#observablestatus)<`boolean`\>
#### Defined in
@@ -1407,7 +1447,7 @@ ___
### useRemoteConfigNumber
-▸ **useRemoteConfigNumber**(`key`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`number`\>
+▸ **useRemoteConfigNumber**(`key`): [`ObservableStatus`](README.md#observablestatus)<`number`\>
Convience method similar to useRemoteConfigValue. Returns a `number` from a Remote Config parameter.
@@ -1419,7 +1459,7 @@ Convience method similar to useRemoteConfigValue. Returns a `number` from a Remo
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`number`\>
+[`ObservableStatus`](README.md#observablestatus)<`number`\>
#### Defined in
@@ -1429,7 +1469,7 @@ ___
### useRemoteConfigString
-▸ **useRemoteConfigString**(`key`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`string`\>
+▸ **useRemoteConfigString**(`key`): [`ObservableStatus`](README.md#observablestatus)<`string`\>
Convience method similar to useRemoteConfigValue. Returns a `string` from a Remote Config parameter.
@@ -1441,7 +1481,7 @@ Convience method similar to useRemoteConfigValue. Returns a `string` from a Remo
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`string`\>
+[`ObservableStatus`](README.md#observablestatus)<`string`\>
#### Defined in
@@ -1451,7 +1491,7 @@ ___
### useRemoteConfigValue
-▸ **useRemoteConfigValue**(`key`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`RemoteConfigValue`\>
+▸ **useRemoteConfigValue**(`key`): [`ObservableStatus`](README.md#observablestatus)<`RemoteConfigValue`\>
Accepts a key and optionally a Remote Config instance. Returns a
Remote Config Value.
@@ -1464,7 +1504,7 @@ Remote Config Value.
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`RemoteConfigValue`\>
+[`ObservableStatus`](README.md#observablestatus)<`RemoteConfigValue`\>
#### Defined in
@@ -1474,7 +1514,7 @@ ___
### useSigninCheck
-▸ **useSigninCheck**(`options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<[`SigninCheckResult`](README.md#signincheckresult)\>
+▸ **useSigninCheck**(`options?`): [`ObservableStatus`](README.md#observablestatus)<[`SigninCheckResult`](README.md#signincheckresult)\>
Subscribe to the signed-in status of a user.
@@ -1514,7 +1554,7 @@ const {status, data: signInCheckResult} = useSigninCheck({forceRefresh: true, re
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<[`SigninCheckResult`](README.md#signincheckresult)\>
+[`ObservableStatus`](README.md#observablestatus)<[`SigninCheckResult`](README.md#signincheckresult)\>
#### Defined in
@@ -1538,7 +1578,7 @@ ___
### useStorageDownloadURL
-▸ **useStorageDownloadURL**<`T`\>(`ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`string` \| `T`\>
+▸ **useStorageDownloadURL**<`T`\>(`ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`string` \| `T`\>
Subscribe to a storage ref's download URL
@@ -1557,7 +1597,7 @@ Subscribe to a storage ref's download URL
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`string` \| `T`\>
+[`ObservableStatus`](README.md#observablestatus)<`string` \| `T`\>
#### Defined in
@@ -1567,7 +1607,7 @@ ___
### useStorageTask
-▸ **useStorageTask**<`T`\>(`task`, `ref`, `options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`UploadTaskSnapshot` \| `T`\>
+▸ **useStorageTask**<`T`\>(`task`, `ref`, `options?`): [`ObservableStatus`](README.md#observablestatus)<`UploadTaskSnapshot` \| `T`\>
Subscribe to the progress of a storage task
@@ -1587,7 +1627,7 @@ Subscribe to the progress of a storage task
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`UploadTaskSnapshot` \| `T`\>
+[`ObservableStatus`](README.md#observablestatus)<`UploadTaskSnapshot` \| `T`\>
#### Defined in
@@ -1617,7 +1657,7 @@ ___
### useUser
-▸ **useUser**<`T`\>(`options?`): [`ObservableStatus`](interfaces/ObservableStatus.md)<`User` \| ``null``\>
+▸ **useUser**<`T`\>(`options?`): [`ObservableStatus`](README.md#observablestatus)<`User` \| ``null``\>
Subscribe to Firebase auth state changes, including token refresh
@@ -1635,7 +1675,7 @@ Subscribe to Firebase auth state changes, including token refresh
#### Returns
-[`ObservableStatus`](interfaces/ObservableStatus.md)<`User` \| ``null``\>
+[`ObservableStatus`](README.md#observablestatus)<`User` \| ``null``\>
#### Defined in
diff --git a/docs/reference/interfaces/FirebaseAppProviderProps.md b/docs/reference/interfaces/FirebaseAppProviderProps.md
new file mode 100644
index 00000000..6ac494a4
--- /dev/null
+++ b/docs/reference/interfaces/FirebaseAppProviderProps.md
@@ -0,0 +1,52 @@
+[ReactFire reference docs](../README.md) / FirebaseAppProviderProps
+
+# Interface: FirebaseAppProviderProps
+
+## Table of contents
+
+### Properties
+
+- [appName](FirebaseAppProviderProps.md#appname)
+- [firebaseApp](FirebaseAppProviderProps.md#firebaseapp)
+- [firebaseConfig](FirebaseAppProviderProps.md#firebaseconfig)
+- [suspense](FirebaseAppProviderProps.md#suspense)
+
+## Properties
+
+### appName
+
+• `Optional` **appName**: `string`
+
+#### Defined in
+
+[src/firebaseApp.tsx:15](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L15)
+
+___
+
+### firebaseApp
+
+• `Optional` **firebaseApp**: `FirebaseApp`
+
+#### Defined in
+
+[src/firebaseApp.tsx:13](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L13)
+
+___
+
+### firebaseConfig
+
+• `Optional` **firebaseConfig**: `FirebaseOptions`
+
+#### Defined in
+
+[src/firebaseApp.tsx:14](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L14)
+
+___
+
+### suspense
+
+• `Optional` **suspense**: `boolean`
+
+#### Defined in
+
+[src/firebaseApp.tsx:16](https://github.com/FirebaseExtended/reactfire/blob/main/src/firebaseApp.tsx#L16)
diff --git a/docs/reference/interfaces/ObservableStatus.md b/docs/reference/interfaces/ObservableStatus.md
deleted file mode 100644
index 39e4ac8e..00000000
--- a/docs/reference/interfaces/ObservableStatus.md
+++ /dev/null
@@ -1,102 +0,0 @@
-[ReactFire reference docs](../README.md) / ObservableStatus
-
-# Interface: ObservableStatus
-
-## Type parameters
-
-| Name |
-| :------ |
-| `T` |
-
-## Table of contents
-
-### Properties
-
-- [data](ObservableStatus.md#data)
-- [error](ObservableStatus.md#error)
-- [firstValuePromise](ObservableStatus.md#firstvaluepromise)
-- [hasEmitted](ObservableStatus.md#hasemitted)
-- [isComplete](ObservableStatus.md#iscomplete)
-- [status](ObservableStatus.md#status)
-
-## Properties
-
-### data
-
-• **data**: `T`
-
-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`.
-
-#### Defined in
-
-[src/useObservable.ts:55](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L55)
-
-___
-
-### error
-
-• **error**: `undefined` \| `Error`
-
-Any error that may have occurred in the underlying observable
-
-#### Defined in
-
-[src/useObservable.ts:59](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L59)
-
-___
-
-### firstValuePromise
-
-• **firstValuePromise**: `Promise`<`void`\>
-
-Promise that resolves after first emit from observable
-
-#### Defined in
-
-[src/useObservable.ts:63](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L63)
-
-___
-
-### hasEmitted
-
-• **hasEmitted**: `boolean`
-
-Indicates whether the hook has emitted a value at some point
-
-If `initialData` is passed in, this will be `true`.
-
-#### Defined in
-
-[src/useObservable.ts:45](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L45)
-
-___
-
-### isComplete
-
-• **isComplete**: `boolean`
-
-If this is `true`, the hook will be emitting no further items.
-
-#### Defined in
-
-[src/useObservable.ts:49](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L49)
-
-___
-
-### status
-
-• **status**: ``"error"`` \| ``"loading"`` \| ``"success"``
-
-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`.
-
-#### Defined in
-
-[src/useObservable.ts:39](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L39)
diff --git a/docs/reference/interfaces/ObservableStatusError.md b/docs/reference/interfaces/ObservableStatusError.md
new file mode 100644
index 00000000..90cde303
--- /dev/null
+++ b/docs/reference/interfaces/ObservableStatusError.md
@@ -0,0 +1,120 @@
+[ReactFire reference docs](../README.md) / ObservableStatusError
+
+# Interface: ObservableStatusError
+
+## Type parameters
+
+| Name |
+| :------ |
+| `T` |
+
+## Hierarchy
+
+- `ObservableStatusBase`<`T`\>
+
+ ↳ **`ObservableStatusError`**
+
+## Table of contents
+
+### Properties
+
+- [data](ObservableStatusError.md#data)
+- [error](ObservableStatusError.md#error)
+- [firstValuePromise](ObservableStatusError.md#firstvaluepromise)
+- [hasEmitted](ObservableStatusError.md#hasemitted)
+- [isComplete](ObservableStatusError.md#iscomplete)
+- [status](ObservableStatusError.md#status)
+
+## Properties
+
+### data
+
+• **data**: `undefined` \| `T`
+
+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`.
+
+#### Inherited from
+
+ObservableStatusBase.data
+
+#### Defined in
+
+[src/useObservable.ts:56](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L56)
+
+___
+
+### error
+
+• **error**: `Error`
+
+#### Overrides
+
+ObservableStatusBase.error
+
+#### Defined in
+
+[src/useObservable.ts:75](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L75)
+
+___
+
+### firstValuePromise
+
+• **firstValuePromise**: `Promise`<`void`\>
+
+Promise that resolves after first emit from observable
+
+#### Inherited from
+
+ObservableStatusBase.firstValuePromise
+
+#### Defined in
+
+[src/useObservable.ts:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L64)
+
+___
+
+### hasEmitted
+
+• **hasEmitted**: `boolean`
+
+Indicates whether the hook has emitted a value at some point
+
+If `initialData` is passed in, this will be `true`.
+
+#### Inherited from
+
+ObservableStatusBase.hasEmitted
+
+#### Defined in
+
+[src/useObservable.ts:46](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L46)
+
+___
+
+### isComplete
+
+• **isComplete**: ``true``
+
+#### Overrides
+
+ObservableStatusBase.isComplete
+
+#### Defined in
+
+[src/useObservable.ts:74](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L74)
+
+___
+
+### status
+
+• **status**: ``"error"``
+
+#### Overrides
+
+ObservableStatusBase.status
+
+#### Defined in
+
+[src/useObservable.ts:73](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L73)
diff --git a/docs/reference/interfaces/ObservableStatusLoading.md b/docs/reference/interfaces/ObservableStatusLoading.md
new file mode 100644
index 00000000..d3f0bcd7
--- /dev/null
+++ b/docs/reference/interfaces/ObservableStatusLoading.md
@@ -0,0 +1,116 @@
+[ReactFire reference docs](../README.md) / ObservableStatusLoading
+
+# Interface: ObservableStatusLoading
+
+## Type parameters
+
+| Name |
+| :------ |
+| `T` |
+
+## Hierarchy
+
+- `ObservableStatusBase`<`T`\>
+
+ ↳ **`ObservableStatusLoading`**
+
+## Table of contents
+
+### Properties
+
+- [data](ObservableStatusLoading.md#data)
+- [error](ObservableStatusLoading.md#error)
+- [firstValuePromise](ObservableStatusLoading.md#firstvaluepromise)
+- [hasEmitted](ObservableStatusLoading.md#hasemitted)
+- [isComplete](ObservableStatusLoading.md#iscomplete)
+- [status](ObservableStatusLoading.md#status)
+
+## Properties
+
+### data
+
+• **data**: `undefined`
+
+#### Overrides
+
+ObservableStatusBase.data
+
+#### Defined in
+
+[src/useObservable.ts:80](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L80)
+
+___
+
+### error
+
+• **error**: `undefined` \| `Error`
+
+Any error that may have occurred in the underlying observable
+
+#### Inherited from
+
+ObservableStatusBase.error
+
+#### Defined in
+
+[src/useObservable.ts:60](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L60)
+
+___
+
+### firstValuePromise
+
+• **firstValuePromise**: `Promise`<`void`\>
+
+Promise that resolves after first emit from observable
+
+#### Inherited from
+
+ObservableStatusBase.firstValuePromise
+
+#### Defined in
+
+[src/useObservable.ts:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L64)
+
+___
+
+### hasEmitted
+
+• **hasEmitted**: ``false``
+
+#### Overrides
+
+ObservableStatusBase.hasEmitted
+
+#### Defined in
+
+[src/useObservable.ts:81](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L81)
+
+___
+
+### isComplete
+
+• **isComplete**: `boolean`
+
+If this is `true`, the hook will be emitting no further items.
+
+#### Inherited from
+
+ObservableStatusBase.isComplete
+
+#### Defined in
+
+[src/useObservable.ts:50](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L50)
+
+___
+
+### status
+
+• **status**: ``"loading"``
+
+#### Overrides
+
+ObservableStatusBase.status
+
+#### Defined in
+
+[src/useObservable.ts:79](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L79)
diff --git a/docs/reference/interfaces/ObservableStatusSuccess.md b/docs/reference/interfaces/ObservableStatusSuccess.md
new file mode 100644
index 00000000..4a8567f9
--- /dev/null
+++ b/docs/reference/interfaces/ObservableStatusSuccess.md
@@ -0,0 +1,120 @@
+[ReactFire reference docs](../README.md) / ObservableStatusSuccess
+
+# Interface: ObservableStatusSuccess
+
+## Type parameters
+
+| Name |
+| :------ |
+| `T` |
+
+## Hierarchy
+
+- `ObservableStatusBase`<`T`\>
+
+ ↳ **`ObservableStatusSuccess`**
+
+## Table of contents
+
+### Properties
+
+- [data](ObservableStatusSuccess.md#data)
+- [error](ObservableStatusSuccess.md#error)
+- [firstValuePromise](ObservableStatusSuccess.md#firstvaluepromise)
+- [hasEmitted](ObservableStatusSuccess.md#hasemitted)
+- [isComplete](ObservableStatusSuccess.md#iscomplete)
+- [status](ObservableStatusSuccess.md#status)
+
+## Properties
+
+### data
+
+• **data**: `T`
+
+#### Overrides
+
+ObservableStatusBase.data
+
+#### Defined in
+
+[src/useObservable.ts:69](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L69)
+
+___
+
+### error
+
+• **error**: `undefined` \| `Error`
+
+Any error that may have occurred in the underlying observable
+
+#### Inherited from
+
+ObservableStatusBase.error
+
+#### Defined in
+
+[src/useObservable.ts:60](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L60)
+
+___
+
+### firstValuePromise
+
+• **firstValuePromise**: `Promise`<`void`\>
+
+Promise that resolves after first emit from observable
+
+#### Inherited from
+
+ObservableStatusBase.firstValuePromise
+
+#### Defined in
+
+[src/useObservable.ts:64](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L64)
+
+___
+
+### hasEmitted
+
+• **hasEmitted**: `boolean`
+
+Indicates whether the hook has emitted a value at some point
+
+If `initialData` is passed in, this will be `true`.
+
+#### Inherited from
+
+ObservableStatusBase.hasEmitted
+
+#### Defined in
+
+[src/useObservable.ts:46](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L46)
+
+___
+
+### isComplete
+
+• **isComplete**: `boolean`
+
+If this is `true`, the hook will be emitting no further items.
+
+#### Inherited from
+
+ObservableStatusBase.isComplete
+
+#### Defined in
+
+[src/useObservable.ts:50](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L50)
+
+___
+
+### status
+
+• **status**: ``"success"``
+
+#### Overrides
+
+ObservableStatusBase.status
+
+#### Defined in
+
+[src/useObservable.ts:68](https://github.com/FirebaseExtended/reactfire/blob/main/src/useObservable.ts#L68)
diff --git a/package-lock.json b/package-lock.json
index 731fe016..bd1883d6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,8 @@
"license": "MIT",
"dependencies": {
"rxfire": "^6.0.3",
- "rxjs": "^6.6.3 || ^7.0.1"
+ "rxjs": "^6.6.3 || ^7.0.1",
+ "use-sync-external-store": "^1.2.0"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.1",
@@ -19,6 +20,7 @@
"@testing-library/react": "^14.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
+ "@types/use-sync-external-store": "^0.0.3",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"@vitejs/plugin-react": "^4.0.1",
@@ -2851,6 +2853,12 @@
"integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==",
"dev": true
},
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==",
+ "dev": true
+ },
"node_modules/@types/yargs": {
"version": "17.0.24",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz",
@@ -9271,8 +9279,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.0",
@@ -9922,7 +9929,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -11898,7 +11904,6 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -14138,6 +14143,14 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/package.json b/package.json
index f4c5bd25..7c5e1d4f 100644
--- a/package.json
+++ b/package.json
@@ -77,6 +77,7 @@
"@testing-library/react": "^14.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
+ "@types/use-sync-external-store": "^0.0.3",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"@vitejs/plugin-react": "^4.0.1",
@@ -107,6 +108,7 @@
},
"dependencies": {
"rxfire": "^6.0.3",
- "rxjs": "^6.6.3 || ^7.0.1"
+ "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 40b05ec2..32d01a2c 100644
--- a/src/SuspenseSubject.ts
+++ b/src/SuspenseSubject.ts
@@ -1,5 +1,6 @@
import { empty, Observable, Subject, Subscriber, Subscription } from 'rxjs';
import { catchError, shareReplay, tap } from 'rxjs/operators';
+import { ObservableStatus } from './useObservable';
export class SuspenseSubject extends Subject {
private _value: T | undefined;
@@ -9,6 +10,8 @@ export class SuspenseSubject extends Subject {
private _error: any = undefined;
private _innerObservable: Observable;
private _warmupSubscription: Subscription;
+ private _immutableStatus: ObservableStatus;
+ private _isComplete = false;
// @ts-expect-error: TODO: double check to see if this is an RXJS thing or if we should listen to TS
private _innerSubscriber: Subscription;
@@ -18,6 +21,16 @@ export class SuspenseSubject extends Subject {
constructor(innerObservable: Observable, private _timeoutWindow: number, private _suspenseEnabled: boolean) {
super();
this._firstEmission = new Promise((resolve) => (this._resolveFirstEmission = resolve));
+
+ this._immutableStatus = {
+ status: 'loading',
+ hasEmitted: false,
+ isComplete: false,
+ data: undefined,
+ error: undefined,
+ firstValuePromise: this._firstEmission
+ };
+
this._innerObservable = innerObservable.pipe(
tap({
next: (v) => {
@@ -28,7 +41,12 @@ export class SuspenseSubject extends Subject {
// resolve the promise, so suspense tries again
this._error = e;
this._resolveFirstEmission();
+ this._updateImmutableStatus();
},
+ complete: () => {
+ this._isComplete = true;
+ this._updateImmutableStatus();
+ }
}),
catchError(() => empty()),
shareReplay(1)
@@ -69,10 +87,27 @@ export class SuspenseSubject extends Subject {
return this._firstEmission;
}
+ private _updateImmutableStatus() {
+ // @ts-expect-error
+ // TS fails here because ObservableStatus defines specific
+ // relationships between the fields. This is difficult to
+ // code for here, so the relationships between the ObservableStatus fields
+ // are mostly checked 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
+ };
+ }
+
private _next(value: T) {
this._hasValue = true;
this._value = value;
this._resolveFirstEmission();
+ this._updateImmutableStatus();
}
private _reset() {
@@ -84,6 +119,7 @@ export class SuspenseSubject extends Subject {
this._value = undefined;
this._error = undefined;
this._firstEmission = new Promise((resolve) => (this._resolveFirstEmission = resolve));
+ this._updateImmutableStatus();
}
_subscribe(subscriber: Subscriber): Subscription {
@@ -97,4 +133,8 @@ export class SuspenseSubject extends Subject {
get ourError() {
return this._error;
}
+
+ get immutableStatus() {
+ return this._immutableStatus;
+ }
}
diff --git a/src/auth.tsx b/src/auth.tsx
index ca00a6c9..d535b894 100644
--- a/src/auth.tsx
+++ b/src/auth.tsx
@@ -201,7 +201,14 @@ function getClaimsObjectValidator(requiredClaims: Claims): ClaimsValidator {
* 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 { data } = useIdTokenResult(user, false);
+ 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
+ }
+
const { claims } = data;
const missingClaims: { [key: string]: { expected: string; actual: string | undefined } } = {};
diff --git a/src/firebaseApp.tsx b/src/firebaseApp.tsx
index a4e73dc3..d7f2d1e9 100644
--- a/src/firebaseApp.tsx
+++ b/src/firebaseApp.tsx
@@ -9,7 +9,7 @@ const DEFAULT_APP_NAME = '[DEFAULT]';
const FirebaseAppContext = React.createContext(undefined);
const SuspenseEnabledContext = React.createContext(false);
-interface FirebaseAppProviderProps {
+export interface FirebaseAppProviderProps {
firebaseApp?: FirebaseApp;
firebaseConfig?: FirebaseOptions;
appName?: string;
diff --git a/src/storage.tsx b/src/storage.tsx
index c3aa1cd7..90ea56c1 100644
--- a/src/storage.tsx
+++ b/src/storage.tsx
@@ -33,7 +33,7 @@ export function useStorageDownloadURL(ref: StorageReference, options
return useObservable(observableId, observable$, options);
}
-type StorageImageProps = {
+export type StorageImageProps = {
storagePath: string;
storage?: FirebaseStorage;
suspense?: boolean;
diff --git a/src/useObservable.ts b/src/useObservable.ts
index e450055b..08e8c748 100644
--- a/src/useObservable.ts
+++ b/src/useObservable.ts
@@ -1,4 +1,5 @@
import * as React from 'react';
+import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { Observable } from 'rxjs';
import { SuspenseSubject } from './SuspenseSubject';
import { useSuspenseEnabledFromConfigAndContext } from './firebaseApp';
@@ -26,7 +27,7 @@ export function preloadObservable(source: Observable, id: string, suspense
}
}
-export interface ObservableStatus {
+interface ObservableStatusBase {
/**
* The loading status.
*
@@ -52,7 +53,7 @@ export interface ObservableStatus {
*
* 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;
+ data: T | undefined;
/**
* Any error that may have occurred in the underlying observable
*/
@@ -63,42 +64,33 @@ export interface ObservableStatus {
firstValuePromise: Promise;
}
-function reducerFactory(observable: SuspenseSubject) {
- return function reducer(state: ObservableStatus, action: 'value' | 'error' | 'complete'): ObservableStatus {
- // always make sure these values are in sync with the observable
- const newState = {
- ...state,
- hasEmitted: state.hasEmitted || observable.hasValue,
- error: observable.ourError,
- firstValuePromise: observable.firstEmission,
- };
- if (observable.hasValue) {
- newState.data = observable.value;
- }
-
- switch (action) {
- case 'value':
- newState.status = 'success';
- return newState;
- case 'error':
- newState.status = 'error';
- return newState;
- case 'complete':
- newState.isComplete = true;
- return newState;
- default:
- throw new Error(`invalid action "${action}"`);
- }
- };
+export interface ObservableStatusSuccess extends ObservableStatusBase {
+ status: 'success';
+ data: T;
}
+export interface ObservableStatusError extends ObservableStatusBase {
+ status: 'error';
+ isComplete: true;
+ error: Error;
+}
+
+export interface ObservableStatusLoading extends ObservableStatusBase {
+ status: 'loading';
+ data: undefined;
+ hasEmitted: false;
+}
+
+export type ObservableStatus = ObservableStatusLoading | ObservableStatusError | ObservableStatusSuccess;
+
export function useObservable(observableId: string, source: Observable, config: ReactFireOptions = {}): ObservableStatus {
- // Register the observable with the cache
if (!observableId) {
throw new Error('cannot call useObservable without an observableId');
}
+
const suspenseEnabled = useSuspenseEnabledFromConfigAndContext(config.suspense);
+ // Register the observable with the cache
const observable = preloadObservable(source, observableId, suspenseEnabled);
// Suspend if suspense is enabled and no initial data exists
@@ -108,31 +100,43 @@ export function useObservable(observableId: string, source: Observa
throw observable.firstEmission;
}
- const initialState: ObservableStatus = {
- status: hasData ? 'success' : 'loading',
- hasEmitted: hasData,
- isComplete: false,
- data: observable.hasValue ? observable.value : config?.initialData ?? config?.startWithValue,
- error: observable.ourError,
- firstValuePromise: observable.firstEmission,
- };
- const [status, dispatch] = React.useReducer, 'value' | 'error' | 'complete'>>(reducerFactory(observable), initialState);
-
- React.useEffect(() => {
- const subscription = observable.subscribe({
- next: () => {
- dispatch('value');
- },
- error: (e) => {
- dispatch('error');
- throw e;
- },
- complete: () => {
- dispatch('complete');
- },
- });
- return () => subscription.unsubscribe();
+ const subscribe = React.useCallback((onStoreChange: () => void) => {
+ const subscription = observable.subscribe({
+ next: () => {
+ onStoreChange();
+ },
+ error: (e) => {
+ onStoreChange();
+ throw e;
+ },
+ complete: () => {
+ onStoreChange();
+ },
+ });
+
+ return () => {
+ subscription.unsubscribe();
+ }
+ }, [observable])
+
+ const getSnapshot = React.useCallback<() => ObservableStatus>(() => {
+ return observable.immutableStatus;
}, [observable]);
- return status;
+ const update = useSyncExternalStore(subscribe, getSnapshot);
+
+ // modify the value if initialData exists
+ if (!observable.hasValue && hasData) {
+ update.data = config?.initialData ?? config?.startWithValue;
+ update.status = 'success';
+ update.hasEmitted = true;
+ }
+
+ // throw an error if there is an error
+ // TODO(jhuleatt) this is the current, tested-for, behavior. But do we actually want it?
+ if (update.error) {
+ throw update.error;
+ }
+
+ return update;
}
diff --git a/test/firestore.test.tsx b/test/firestore.test.tsx
index 16ca77e2..f83aee19 100644
--- a/test/firestore.test.tsx
+++ b/test/firestore.test.tsx
@@ -17,6 +17,7 @@ import { baseConfig } from './appConfig';
import { randomString } from './test-utils';
import { addDoc, collection, doc, getFirestore, query, setDoc, connectFirestoreEmulator, where, getDoc } from 'firebase/firestore';
+import type { DocumentReference } from 'firebase/firestore';
describe('Firestore', () => {
const app = initializeApp(baseConfig, 'firestore-test-suite');
@@ -99,6 +100,50 @@ describe('Firestore', () => {
expect(result.current.status).toEqual('success');
expect(result.current.data).toBeUndefined();
});
+
+ it('goes back into a loading state if you swap the query', async () => {
+ const mockData = { a: 'hello' };
+ const otherMockData = { a: 'goodbye' };
+
+ const collectionRef = collection(db, randomString());
+ const firstRef = doc(collectionRef, randomString());
+ const secondRef = doc(collectionRef, randomString());
+
+ await setDoc(firstRef, mockData);
+ await setDoc(secondRef, otherMockData);
+
+ const { result, rerender } = renderHook(
+ (props: { docRef: DocumentReference }) => {
+ const update = useFirestoreDocData(props.docRef, { idField: 'id' });
+
+ // important!! check that the hook doesn't show stale data
+ // for example, if props.docRef.id is a new ref but update.data.id is data for the old ref
+ if (update.status === 'success') {
+ expect(update.data.id).toEqual(props.docRef.id);
+ }
+
+ return update;
+ },
+ {
+ wrapper: Provider,
+ initialProps: { docRef: firstRef },
+ }
+ );
+
+ // ensure first ref's data is loaded
+ await waitFor(() => expect(result.current.status).toEqual('success'));
+ expect(result.current.data).toBeDefined();
+ expect(result.current.data.a).toEqual(mockData.a);
+
+ // re-render the hook with the second reference
+ rerender({ docRef: secondRef });
+
+ // ensure second ref's data is loaded
+ await waitFor(() => {
+ expect(result.current.data).toBeDefined();
+ expect(result.current.data.a).toEqual(otherMockData.a);
+ });
+ });
});
describe('useFirestoreDocOnce', () => {
diff --git a/test/useObservable.test.tsx b/test/useObservable.test.tsx
index 5869e589..c00bd333 100644
--- a/test/useObservable.test.tsx
+++ b/test/useObservable.test.tsx
@@ -114,6 +114,20 @@ describe('useObservable', () => {
await findByTestId('comp');
expect(element).toHaveTextContent(startVal);
});
+
+ it('sets isComplete', async () => {
+ const observable$: Subject = new Subject();
+
+ const { result } = renderHook(() => useObservable('isComplete test', observable$, { suspense: false }));
+
+ expect(result.current.isComplete).toEqual(false);
+
+ act(() => observable$.next('val'));
+ expect(result.current.isComplete).toEqual(false);
+
+ act(() => observable$.complete());
+ await waitFor(() => expect(result.current.isComplete).toEqual(true));
+ });
});
describe('Suspense Mode', () => {