reactfire 4.2.3 → 4.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,4 +4,4 @@ export interface SuspensePerfProps {
4
4
  traceId: string;
5
5
  fallback: React.ReactNode;
6
6
  }
7
- export declare function SuspenseWithPerf({ children, traceId, fallback }: SuspensePerfProps): JSX.Element;
7
+ export declare function SuspenseWithPerf({ children, traceId, fallback }: SuspensePerfProps): React.ReactElement;
package/dist/sdk.d.ts CHANGED
@@ -20,34 +20,34 @@ export declare const FunctionsSdkContext: React.Context<Functions | undefined>;
20
20
  export declare const StorageSdkContext: React.Context<FirebaseStorage | undefined>;
21
21
  export declare const PerformanceSdkContext: React.Context<FirebasePerformance | undefined>;
22
22
  export declare const RemoteConfigSdkContext: React.Context<RemoteConfig | undefined>;
23
- declare type FirebaseSdks = Analytics | AppCheck | Auth | Database | Firestore | FirebasePerformance | FirebaseStorage | Functions | RemoteConfig;
23
+ type FirebaseSdks = Analytics | AppCheck | Auth | Database | Firestore | FirebasePerformance | FirebaseStorage | Functions | RemoteConfig;
24
24
  export declare const AppCheckProvider: (props: React.PropsWithChildren<{
25
25
  sdk: AppCheck;
26
- }>) => JSX.Element;
26
+ }>) => React.ReactElement;
27
27
  export declare const AuthProvider: (props: React.PropsWithChildren<{
28
28
  sdk: Auth;
29
- }>) => JSX.Element;
29
+ }>) => React.ReactElement;
30
30
  export declare const AnalyticsProvider: (props: React.PropsWithChildren<{
31
31
  sdk: Analytics;
32
- }>) => JSX.Element;
32
+ }>) => React.ReactElement;
33
33
  export declare const DatabaseProvider: (props: React.PropsWithChildren<{
34
34
  sdk: Database;
35
- }>) => JSX.Element;
35
+ }>) => React.ReactElement;
36
36
  export declare const FirestoreProvider: (props: React.PropsWithChildren<{
37
37
  sdk: Firestore;
38
- }>) => JSX.Element;
38
+ }>) => React.ReactElement;
39
39
  export declare const FunctionsProvider: (props: React.PropsWithChildren<{
40
40
  sdk: Functions;
41
- }>) => JSX.Element;
41
+ }>) => React.ReactElement;
42
42
  export declare const PerformanceProvider: (props: React.PropsWithChildren<{
43
43
  sdk: FirebasePerformance;
44
- }>) => JSX.Element;
44
+ }>) => React.ReactElement;
45
45
  export declare const StorageProvider: (props: React.PropsWithChildren<{
46
46
  sdk: FirebaseStorage;
47
- }>) => JSX.Element;
47
+ }>) => React.ReactElement;
48
48
  export declare const RemoteConfigProvider: (props: React.PropsWithChildren<{
49
49
  sdk: RemoteConfig;
50
- }>) => JSX.Element;
50
+ }>) => React.ReactElement;
51
51
  export declare const useAppCheck: () => AppCheck;
52
52
  export declare const useAuth: () => Auth;
53
53
  export declare const useAnalytics: () => Analytics;
@@ -57,7 +57,7 @@ export declare const useFunctions: () => Functions;
57
57
  export declare const usePerformance: () => FirebasePerformance;
58
58
  export declare const useStorage: () => FirebaseStorage;
59
59
  export declare const useRemoteConfig: () => RemoteConfig;
60
- declare type InitSdkHook<Sdk extends FirebaseSdks> = (initializer: (firebaseApp: FirebaseApp) => Promise<Sdk>, options?: ReactFireOptions<Sdk>) => ObservableStatus<Sdk>;
60
+ type InitSdkHook<Sdk extends FirebaseSdks> = (initializer: (firebaseApp: FirebaseApp) => Promise<Sdk>, options?: ReactFireOptions<Sdk>) => ObservableStatus<Sdk>;
61
61
  export declare const useInitAppCheck: InitSdkHook<AppCheck>;
62
62
  export declare const useInitAuth: InitSdkHook<Auth>;
63
63
  export declare const useInitAnalytics: InitSdkHook<Analytics>;
package/dist/storage.d.ts CHANGED
@@ -16,11 +16,10 @@ export declare function useStorageTask<T = unknown>(task: UploadTask, ref: Stora
16
16
  * @param options
17
17
  */
18
18
  export declare function useStorageDownloadURL<T = string>(ref: StorageReference, options?: ReactFireOptions<T>): ObservableStatus<string | T>;
19
- declare type StorageImageProps = {
19
+ export type StorageImageProps = {
20
20
  storagePath: string;
21
21
  storage?: FirebaseStorage;
22
22
  suspense?: boolean;
23
- placeHolder?: JSX.Element;
23
+ placeHolder?: React.ReactNode;
24
24
  };
25
- export declare function StorageImage(props: StorageImageProps & React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>): JSX.Element;
26
- export {};
25
+ export declare function StorageImage(props: StorageImageProps & React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>): React.JSX.Element;
@@ -1,8 +1,8 @@
1
1
  import { Observable } from 'rxjs';
2
2
  import { SuspenseSubject } from './SuspenseSubject';
3
3
  import { ReactFireOptions } from './';
4
- export declare function preloadObservable<T>(source: Observable<T>, id: string): SuspenseSubject<T>;
5
- export interface ObservableStatus<T> {
4
+ export declare function preloadObservable<T>(source: Observable<T>, id: string, suspenseEnabled?: boolean): SuspenseSubject<T>;
5
+ interface ObservableStatusBase<T> {
6
6
  /**
7
7
  * The loading status.
8
8
  *
@@ -28,7 +28,7 @@ export interface ObservableStatus<T> {
28
28
  *
29
29
  * 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`.
30
30
  */
31
- data: T;
31
+ data: T | undefined;
32
32
  /**
33
33
  * Any error that may have occurred in the underlying observable
34
34
  */
@@ -38,4 +38,20 @@ export interface ObservableStatus<T> {
38
38
  */
39
39
  firstValuePromise: Promise<void>;
40
40
  }
41
+ export interface ObservableStatusSuccess<T> extends ObservableStatusBase<T> {
42
+ status: 'success';
43
+ data: T;
44
+ }
45
+ export interface ObservableStatusError<T> extends ObservableStatusBase<T> {
46
+ status: 'error';
47
+ isComplete: true;
48
+ error: Error;
49
+ }
50
+ export interface ObservableStatusLoading<T> extends ObservableStatusBase<T> {
51
+ status: 'loading';
52
+ data: undefined;
53
+ hasEmitted: false;
54
+ }
55
+ export type ObservableStatus<T> = ObservableStatusLoading<T> | ObservableStatusError<T> | ObservableStatusSuccess<T>;
41
56
  export declare function useObservable<T = unknown>(observableId: string, source: Observable<T>, config?: ReactFireOptions): ObservableStatus<T>;
57
+ export {};
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.2.3",
2
+ "version": "4.2.4",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
5
  "main": "dist/index.umd.cjs",
@@ -36,7 +36,7 @@
36
36
  "docs:fork": "typedoc --options typedoc.json --gitRemote upstream && markdown-toc -i docs/use.md"
37
37
  },
38
38
  "peerDependencies": {
39
- "firebase": "^9.0.0",
39
+ "firebase": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || next",
40
40
  "react": ">=16 || experimental"
41
41
  },
42
42
  "husky": {
@@ -71,43 +71,44 @@
71
71
  }
72
72
  ],
73
73
  "devDependencies": {
74
- "@rollup/plugin-typescript": "^8.3.4",
75
- "@size-limit/preset-small-lib": "^7.0.8",
76
- "@testing-library/jest-dom": "^5.16.4",
77
- "@testing-library/react": "^12.1.5",
78
- "@testing-library/react-hooks": "^8.0.1",
79
- "@types/react": "^17.0.2",
80
- "@types/react-dom": "^17.0.2",
81
- "@typescript-eslint/eslint-plugin": "^5.32.0",
82
- "@typescript-eslint/parser": "^5.32.0",
83
- "@vitejs/plugin-react": "^2.0.0",
84
- "@vitest/ui": "^0.20.3",
85
- "cross-fetch": "^3.1.5",
86
- "eslint": "^8.20.0",
87
- "eslint-plugin-no-only-tests": "^2.6.0",
88
- "eslint-plugin-react": "^7.30.1",
74
+ "@rollup/plugin-typescript": "^12.3.0",
75
+ "@size-limit/preset-small-lib": "^8.2.6",
76
+ "@testing-library/jest-dom": "^5.16.5",
77
+ "@testing-library/react": "^14.0.0",
78
+ "@types/react": "^18.2.0",
79
+ "@types/react-dom": "^18.2.0",
80
+ "@types/use-sync-external-store": "^0.0.3",
81
+ "@typescript-eslint/eslint-plugin": "^5.60.1",
82
+ "@typescript-eslint/parser": "^5.60.1",
83
+ "@vitejs/plugin-react": "^6.0.2",
84
+ "@vitest/ui": "^4.1.9",
85
+ "cross-fetch": "^3.1.6",
86
+ "eslint": "^8.43.0",
87
+ "eslint-plugin-no-only-tests": "^3.1.0",
88
+ "eslint-plugin-react": "^7.32.2",
89
89
  "eslint-plugin-react-hooks": "^4.6.0",
90
- "firebase": "^9.9.0",
91
- "firebase-tools": "^11.3.0",
90
+ "firebase": "^11.10.0",
91
+ "firebase-tools": "^15.22.3",
92
92
  "globalthis": "^1.0.3",
93
- "husky": "^8.0.1",
94
- "jest-environment-jsdom": "^28.1.3",
95
- "jsdom": "^20.0.0",
93
+ "husky": "^8.0.3",
94
+ "jest-environment-jsdom": "^29.5.0",
95
+ "jsdom": "^22.1.0",
96
96
  "markdown-toc": "^1.2.0",
97
- "prettier": "^2.7.1",
98
- "react": "^17.0.2",
99
- "react-dom": "^17.0.2",
100
- "react-test-renderer": "^17.0.2",
101
- "rollup-plugin-visualizer": "^5.7.1",
102
- "size-limit": "^7.0.8",
103
- "typedoc": "^0.23.8",
104
- "typedoc-plugin-markdown": "^3.13.3",
105
- "typescript": "^4.7.4",
106
- "vite": "^3.0.2",
107
- "vitest": "^0.18.1"
97
+ "prettier": "^3.8.4",
98
+ "react": "^18.2.0",
99
+ "react-dom": "^18.2.0",
100
+ "react-test-renderer": "^18.2.0",
101
+ "rollup-plugin-visualizer": "^5.9.2",
102
+ "size-limit": "^8.2.6",
103
+ "typedoc": "^0.28.19",
104
+ "typedoc-plugin-markdown": "^4.12.0",
105
+ "typescript": "^6.0.3",
106
+ "vite": "^8.0.16",
107
+ "vitest": "^4.1.9"
108
108
  },
109
109
  "dependencies": {
110
- "rxfire": "^6.0.3",
111
- "rxjs": "^6.6.3 || ^7.0.1"
110
+ "rxfire": "^6.1.0",
111
+ "rxjs": "^6.6.3 || ^7.0.1",
112
+ "use-sync-external-store": "^1.2.0"
112
113
  }
113
114
  }
@@ -1,23 +1,36 @@
1
1
  import { empty, Observable, Subject, Subscriber, Subscription } from 'rxjs';
2
2
  import { catchError, shareReplay, tap } from 'rxjs/operators';
3
+ import { ObservableStatus } from './useObservable';
3
4
 
4
5
  export class SuspenseSubject<T> extends Subject<T> {
5
6
  private _value: T | undefined;
6
7
  private _hasValue = false;
7
- private _timeoutHandler: NodeJS.Timeout;
8
+ private _timeoutHandler: ReturnType<typeof setTimeout>;
8
9
  private _firstEmission: Promise<void>;
9
10
  private _error: any = undefined;
10
11
  private _innerObservable: Observable<T>;
11
12
  private _warmupSubscription: Subscription;
13
+ private _immutableStatus: ObservableStatus<T>;
14
+ private _isComplete = false;
12
15
 
13
16
  // @ts-expect-error: TODO: double check to see if this is an RXJS thing or if we should listen to TS
14
17
  private _innerSubscriber: Subscription;
15
18
  // @ts-expect-error: TODO: double check to see if this is an RXJS thing or if we should listen to TS
16
19
  private _resolveFirstEmission: () => void;
17
20
 
18
- constructor(innerObservable: Observable<T>, private _timeoutWindow: number) {
21
+ constructor(innerObservable: Observable<T>, private _timeoutWindow: number, private _suspenseEnabled: boolean) {
19
22
  super();
20
23
  this._firstEmission = new Promise<void>((resolve) => (this._resolveFirstEmission = resolve));
24
+
25
+ this._immutableStatus = {
26
+ status: 'loading',
27
+ hasEmitted: false,
28
+ isComplete: false,
29
+ data: undefined,
30
+ error: undefined,
31
+ firstValuePromise: this._firstEmission
32
+ };
33
+
21
34
  this._innerObservable = innerObservable.pipe(
22
35
  tap({
23
36
  next: (v) => {
@@ -28,7 +41,12 @@ export class SuspenseSubject<T> extends Subject<T> {
28
41
  // resolve the promise, so suspense tries again
29
42
  this._error = e;
30
43
  this._resolveFirstEmission();
44
+ this._updateImmutableStatus();
31
45
  },
46
+ complete: () => {
47
+ this._isComplete = true;
48
+ this._updateImmutableStatus();
49
+ }
32
50
  }),
33
51
  catchError(() => empty()),
34
52
  shareReplay(1)
@@ -38,7 +56,12 @@ export class SuspenseSubject<T> extends Subject<T> {
38
56
 
39
57
  // set a timeout for resetting the cache, subscriptions will cancel the timeout
40
58
  // and reschedule again on unsubscribe
41
- this._timeoutHandler = setTimeout(this._reset.bind(this), this._timeoutWindow);
59
+ if (this._suspenseEnabled) {
60
+ // Noop if suspense is enabled
61
+ this._timeoutHandler = setTimeout(() => {}, this._timeoutWindow);
62
+ } else {
63
+ this._timeoutHandler = setTimeout(this._reset.bind(this), this._timeoutWindow);
64
+ }
42
65
  }
43
66
 
44
67
  get hasValue(): boolean {
@@ -64,10 +87,27 @@ export class SuspenseSubject<T> extends Subject<T> {
64
87
  return this._firstEmission;
65
88
  }
66
89
 
90
+ private _updateImmutableStatus() {
91
+ // @ts-expect-error
92
+ // TS fails here because ObservableStatus defines specific
93
+ // relationships between the fields. This is difficult to
94
+ // code for here, so the relationships between the ObservableStatus fields
95
+ // are mostly checked in tests instead
96
+ this._immutableStatus = {
97
+ status: this._error ? 'error' : (this._hasValue ? 'success' : 'loading'),
98
+ hasEmitted: this._hasValue,
99
+ isComplete: this._isComplete,
100
+ data: this._value,
101
+ error: this._error,
102
+ firstValuePromise: this._firstEmission
103
+ };
104
+ }
105
+
67
106
  private _next(value: T) {
68
107
  this._hasValue = true;
69
108
  this._value = value;
70
109
  this._resolveFirstEmission();
110
+ this._updateImmutableStatus();
71
111
  }
72
112
 
73
113
  private _reset() {
@@ -79,6 +119,7 @@ export class SuspenseSubject<T> extends Subject<T> {
79
119
  this._value = undefined;
80
120
  this._error = undefined;
81
121
  this._firstEmission = new Promise<void>((resolve) => (this._resolveFirstEmission = resolve));
122
+ this._updateImmutableStatus();
82
123
  }
83
124
 
84
125
  _subscribe(subscriber: Subscriber<T>): Subscription {
@@ -92,4 +133,8 @@ export class SuspenseSubject<T> extends Subject<T> {
92
133
  get ourError() {
93
134
  return this._error;
94
135
  }
136
+
137
+ get immutableStatus() {
138
+ return this._immutableStatus;
139
+ }
95
140
  }
package/src/auth.tsx CHANGED
@@ -25,10 +25,20 @@ export function useUser<T = unknown>(options?: ReactFireOptions<T>): ObservableS
25
25
  const observableId = `auth:user:${auth.name}`;
26
26
  const observable$ = user(auth);
27
27
 
28
- return useObservable(observableId, observable$, options);
28
+ const _options: ReactFireOptions<T> = { ...options };
29
+
30
+ // If a user is already signed in, seed initialData so consumers see the user
31
+ // synchronously on the first render without waiting for the async observable.
32
+ // We only do this when currentUser is truthy to avoid masking the uninitialized
33
+ // (null before auth has loaded from storage) case as "signed out".
34
+ if (auth.currentUser && !('initialData' in _options) && !('startWithValue' in _options)) {
35
+ _options.initialData = auth.currentUser as unknown as T;
36
+ }
37
+
38
+ return useObservable(observableId, observable$, _options);
29
39
  }
30
40
 
31
- export function useIdTokenResult(user: User, forceRefresh: boolean = false, options?: ReactFireOptions<IdTokenResult>): ObservableStatus<IdTokenResult> {
41
+ export function useIdTokenResult(user: User, forceRefresh = false, options?: ReactFireOptions<IdTokenResult>): ObservableStatus<IdTokenResult> {
32
42
  if (!user) {
33
43
  throw new Error('you must provide a user');
34
44
  }
@@ -173,7 +183,7 @@ export function useSigninCheck(
173
183
  })
174
184
  );
175
185
 
176
- return useObservable(observableId, observable);
186
+ return useObservable(observableId, observable, options);
177
187
  }
178
188
 
179
189
  function getClaimsObjectValidator(requiredClaims: Claims): ClaimsValidator {
@@ -201,7 +211,14 @@ function getClaimsObjectValidator(requiredClaims: Claims): ClaimsValidator {
201
211
  * Meant for Concurrent mode only (`<FirebaseAppProvider suspense=true />`). [More detail](https://github.com/FirebaseExtended/reactfire/issues/325#issuecomment-827654376).
202
212
  */
203
213
  export function ClaimsCheck({ user, fallback, children, requiredClaims }: ClaimsCheckProps) {
204
- const { data } = useIdTokenResult(user, false);
214
+ const { data, status, error } = useIdTokenResult(user, false);
215
+
216
+ if (status === 'loading') {
217
+ throw new Error('ClaimsCheck must be run in Suspense mode');
218
+ } else if (status === 'error') {
219
+ throw error
220
+ }
221
+
205
222
  const { claims } = data;
206
223
  const missingClaims: { [key: string]: { expected: string; actual: string | undefined } } = {};
207
224
 
@@ -237,7 +254,7 @@ export function ClaimsCheck({ user, fallback, children, requiredClaims }: Claims
237
254
  *
238
255
  * Meant for Concurrent mode only (`<FirebaseAppProvider suspense=true />`). [More detail](https://github.com/FirebaseExtended/reactfire/issues/325#issuecomment-827654376).
239
256
  */
240
- export function AuthCheck({ fallback, children, requiredClaims }: AuthCheckProps): JSX.Element {
257
+ export function AuthCheck({ fallback, children, requiredClaims }: AuthCheckProps): React.ReactElement {
241
258
  const { data: user } = useUser<User>();
242
259
 
243
260
  const suspenseMode = useSuspenseEnabledFromConfigAndContext();
@@ -3,13 +3,15 @@ import { getApps, initializeApp, registerVersion } from 'firebase/app';
3
3
 
4
4
  import type { FirebaseApp, FirebaseOptions } from 'firebase/app';
5
5
 
6
+ declare const process: { env: Record<string, string | undefined> };
7
+
6
8
  // INVESTIGATE I don't like magic strings, can we have export this in js-sdk?
7
9
  const DEFAULT_APP_NAME = '[DEFAULT]';
8
10
 
9
11
  const FirebaseAppContext = React.createContext<FirebaseApp | undefined>(undefined);
10
12
  const SuspenseEnabledContext = React.createContext<boolean>(false);
11
13
 
12
- interface FirebaseAppProviderProps {
14
+ export interface FirebaseAppProviderProps {
13
15
  firebaseApp?: FirebaseApp;
14
16
  firebaseConfig?: FirebaseOptions;
15
17
  appName?: string;
@@ -65,7 +67,7 @@ export function useIsSuspenseEnabled(): boolean {
65
67
  }
66
68
 
67
69
  export function useSuspenseEnabledFromConfigAndContext(suspenseFromConfig?: boolean): boolean {
68
- let suspenseFromContext = React.useContext(SuspenseEnabledContext);
70
+ const suspenseFromContext = React.useContext(SuspenseEnabledContext);
69
71
 
70
72
  // prioritize config over context
71
73
  if (suspenseFromConfig !== undefined) {
package/src/firestore.tsx CHANGED
@@ -6,13 +6,13 @@ import { first } from 'rxjs/operators';
6
6
  import { Query as FirestoreQuery, QuerySnapshot, DocumentReference, queryEqual, DocumentData, DocumentSnapshot } from 'firebase/firestore';
7
7
 
8
8
  // Since we're side-effect free, we need to ensure our observableId cache is global
9
- const cachedQueries: Array<FirestoreQuery> = (globalThis as any as ReactFireGlobals)._reactFireFirestoreQueryCache || [];
9
+ const cachedQueries: Array<FirestoreQuery<any>> = (globalThis as any as ReactFireGlobals)._reactFireFirestoreQueryCache || [];
10
10
 
11
11
  if (!(globalThis as any as ReactFireGlobals)._reactFireFirestoreQueryCache) {
12
12
  (globalThis as any as ReactFireGlobals)._reactFireFirestoreQueryCache = cachedQueries;
13
13
  }
14
14
 
15
- function getUniqueIdForFirestoreQuery(query: FirestoreQuery) {
15
+ function getUniqueIdForFirestoreQuery<T = DocumentData>(query: FirestoreQuery<T>) {
16
16
  const index = cachedQueries.findIndex((cachedQuery) => queryEqual(cachedQuery, query));
17
17
  if (index > -1) {
18
18
  return index;
@@ -30,12 +30,12 @@ export async function preloadFirestoreDoc(refProvider: () => Promise<DocumentRef
30
30
  return preloadObservable(doc(ref), getDocObservableId(ref));
31
31
  }
32
32
 
33
- function getDocObservableId(ref: DocumentReference) {
33
+ function getDocObservableId<T = DocumentData>(ref: DocumentReference<T>) {
34
34
  return `firestore:doc:${ref.firestore.app.name}:${ref.path}`;
35
35
  }
36
36
 
37
37
  /**
38
- * Suscribe to Firestore Document changes
38
+ * Subscribe to Firestore Document changes
39
39
  *
40
40
  * You can preload data for this hook by calling `preloadFirestoreDoc`
41
41
  */
@@ -57,27 +57,27 @@ export function useFirestoreDocOnce<T = DocumentData>(ref: DocumentReference<T>,
57
57
  }
58
58
 
59
59
  /**
60
- * Suscribe to Firestore Document changes and unwrap the document into a plain object
60
+ * Subscribe to Firestore Document changes and unwrap the document into a plain object
61
61
  */
62
- export function useFirestoreDocData<T = unknown>(ref: DocumentReference<T>, options?: ReactFireOptions<T>): ObservableStatus<T> {
62
+ export function useFirestoreDocData<T = unknown>(ref: DocumentReference<T>, options?: ReactFireOptions<T>): ObservableStatus<T | undefined> {
63
63
  const idField = options ? checkIdField(options) : 'NO_ID_FIELD';
64
64
 
65
65
  const observableId = `firestore:docData:${ref.firestore.app.name}:${ref.path}:idField=${idField}`;
66
66
  const observable = docData(ref, { idField });
67
67
 
68
- return useObservable(observableId, observable, options);
68
+ return useObservable(observableId, observable, options) as ObservableStatus<T>;
69
69
  }
70
70
 
71
71
  /**
72
72
  * Get a Firestore document, unwrap the document into a plain object, and don't subscribe to changes
73
73
  */
74
- export function useFirestoreDocDataOnce<T = unknown>(ref: DocumentReference<T>, options?: ReactFireOptions<T>): ObservableStatus<T> {
74
+ export function useFirestoreDocDataOnce<T = unknown>(ref: DocumentReference<T>, options?: ReactFireOptions<T>): ObservableStatus<T | undefined> {
75
75
  const idField = options ? checkIdField(options) : 'NO_ID_FIELD';
76
76
 
77
77
  const observableId = `firestore:docDataOnce:${ref.firestore.app.name}:${ref.path}:idField=${idField}`;
78
78
  const observable$ = docData(ref, { idField }).pipe(first());
79
79
 
80
- return useObservable(observableId, observable$, options);
80
+ return useObservable(observableId, observable$, options) as ObservableStatus<T>;
81
81
  }
82
82
 
83
83
  /**
@@ -6,20 +6,21 @@ export interface SuspensePerfProps {
6
6
  fallback: React.ReactNode;
7
7
  }
8
8
 
9
- export function SuspenseWithPerf({ children, traceId, fallback }: SuspensePerfProps): JSX.Element {
9
+ export function SuspenseWithPerf({ children, traceId, fallback }: SuspensePerfProps): React.ReactElement {
10
10
  // TODO: Should this import firebase/performance?
11
11
 
12
- const entries = performance?.getEntriesByName?.(traceId, 'measure') || [];
12
+ const perf = typeof globalThis !== 'undefined' ? globalThis.performance : undefined;
13
+ const entries = perf?.getEntriesByName?.(traceId, 'measure') || [];
13
14
  const startMarkName = `_${traceId}Start[${entries.length}]`;
14
15
  const endMarkName = `_${traceId}End[${entries.length}]`;
15
16
 
16
17
  const Fallback = () => {
17
- React.useLayoutEffect(() => {
18
- performance?.mark?.(startMarkName);
18
+ React.useEffect(() => {
19
+ perf?.mark?.(startMarkName);
19
20
 
20
21
  return () => {
21
- performance?.mark?.(endMarkName);
22
- performance?.measure?.(traceId, startMarkName, endMarkName);
22
+ perf?.mark?.(endMarkName);
23
+ perf?.measure?.(traceId, startMarkName, endMarkName);
23
24
  };
24
25
  }, []);
25
26
 
package/src/sdk.tsx CHANGED
@@ -28,7 +28,7 @@ export const RemoteConfigSdkContext = React.createContext<RemoteConfig | undefin
28
28
  type FirebaseSdks = Analytics | AppCheck | Auth | Database | Firestore | FirebasePerformance | FirebaseStorage | Functions | RemoteConfig;
29
29
 
30
30
  function getSdkProvider<Sdk extends FirebaseSdks>(SdkContext: React.Context<Sdk | undefined>) {
31
- return function SdkProvider(props: React.PropsWithChildren<{ sdk: Sdk }>) {
31
+ return function SdkProvider(props: React.PropsWithChildren<{ sdk: Sdk }>): React.ReactElement {
32
32
  if (!props.sdk) throw new Error('no sdk provided');
33
33
 
34
34
  const contextualAppName = useFirebaseApp().name;
package/src/storage.tsx CHANGED
@@ -33,11 +33,11 @@ export function useStorageDownloadURL<T = string>(ref: StorageReference, options
33
33
  return useObservable(observableId, observable$, options);
34
34
  }
35
35
 
36
- type StorageImageProps = {
36
+ export type StorageImageProps = {
37
37
  storagePath: string;
38
38
  storage?: FirebaseStorage;
39
39
  suspense?: boolean;
40
- placeHolder?: JSX.Element;
40
+ placeHolder?: React.ReactNode;
41
41
  };
42
42
 
43
43
  function StorageFromContext(props: StorageImageProps & React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>) {
@@ -48,8 +48,8 @@ function StorageFromContext(props: StorageImageProps & React.DetailedHTMLProps<R
48
48
  return <INTERNALStorageImage {...props} />;
49
49
  }
50
50
 
51
- function INTERNALStorageImage(props: StorageImageProps & React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>): JSX.Element {
52
- let { storage, storagePath, suspense, placeHolder, ...imgProps } = props;
51
+ function INTERNALStorageImage(props: StorageImageProps & React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>): React.ReactNode {
52
+ const { storage, storagePath, suspense, placeHolder, ...imgProps } = props;
53
53
 
54
54
  const reactfireOptions: ReactFireOptions<string> = {
55
55
  suspense: useSuspenseEnabledFromConfigAndContext(suspense),
@@ -76,7 +76,7 @@ function INTERNALStorageImage(props: StorageImageProps & React.DetailedHTMLProps
76
76
  }
77
77
 
78
78
  export function StorageImage(props: StorageImageProps & React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>) {
79
- let { storage } = props;
79
+ const { storage } = props;
80
80
 
81
81
  if (storage) {
82
82
  return <INTERNALStorageImage {...props} />;