reactfire 4.0.0-exp.cb3fede → 4.0.0-exp.eb1428b

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.
@@ -63,55 +63,75 @@ export interface ObservableStatus<T> {
63
63
  firstValuePromise: Promise<void>;
64
64
  }
65
65
 
66
+ function reducerFactory<T>(observable: SuspenseSubject<T>) {
67
+ return function reducer(state: ObservableStatus<T>, action: 'value' | 'error' | 'complete'): ObservableStatus<T> {
68
+ // always make sure these values are in sync with the observable
69
+ const newState = {
70
+ ...state,
71
+ hasEmitted: state.hasEmitted || observable.hasValue,
72
+ error: observable.ourError,
73
+ firstValuePromise: observable.firstEmission,
74
+ };
75
+ if (observable.hasValue) {
76
+ newState.data = observable.value;
77
+ }
78
+
79
+ switch (action) {
80
+ case 'value':
81
+ newState.status = 'success';
82
+ return newState;
83
+ case 'error':
84
+ newState.status = 'error';
85
+ return newState;
86
+ case 'complete':
87
+ newState.isComplete = true;
88
+ return newState;
89
+ default:
90
+ throw new Error(`invalid action "${action}"`);
91
+ }
92
+ };
93
+ }
94
+
66
95
  export function useObservable<T = unknown>(observableId: string, source: Observable<T>, config: ReactFireOptions = {}): ObservableStatus<T> {
96
+ // Register the observable with the cache
67
97
  if (!observableId) {
68
98
  throw new Error('cannot call useObservable without an observableId');
69
99
  }
70
100
  const observable = preloadObservable(source, observableId);
71
101
 
102
+ // Suspend if suspense is enabled and no initial data exists
72
103
  const hasInitialData = config.hasOwnProperty('initialData') || config.hasOwnProperty('startWithValue');
73
-
104
+ const hasData = observable.hasValue || hasInitialData;
74
105
  const suspenseEnabled = useSuspenseEnabledFromConfigAndContext(config.suspense);
75
-
76
- if (suspenseEnabled === true && !observable.hasValue && (!config?.initialData ?? !config?.startWithValue)) {
106
+ if (suspenseEnabled === true && !hasData) {
77
107
  throw observable.firstEmission;
78
108
  }
79
109
 
80
- const [latest, setValue] = React.useState(() => (observable.hasValue ? observable.value : config.initialData ?? config.startWithValue));
81
- const [isComplete, setIsComplete] = React.useState(false);
82
- const [hasError, setHasError] = React.useState(false);
110
+ const initialState: ObservableStatus<T> = {
111
+ status: hasData ? 'success' : 'loading',
112
+ hasEmitted: hasData,
113
+ isComplete: false,
114
+ data: observable.hasValue ? observable.value : config?.initialData ?? config?.startWithValue,
115
+ error: observable.ourError,
116
+ firstValuePromise: observable.firstEmission,
117
+ };
118
+ const [status, dispatch] = React.useReducer<React.Reducer<ObservableStatus<T>, 'value' | 'error' | 'complete'>>(reducerFactory<T>(observable), initialState);
119
+
83
120
  React.useEffect(() => {
84
121
  const subscription = observable.subscribe({
85
- next: (v) => {
86
- setValue(() => v);
122
+ next: () => {
123
+ dispatch('value');
87
124
  },
88
125
  error: (e) => {
89
- setHasError(true);
126
+ dispatch('error');
90
127
  throw e;
91
128
  },
92
129
  complete: () => {
93
- setIsComplete(true);
130
+ dispatch('complete');
94
131
  },
95
132
  });
96
133
  return () => subscription.unsubscribe();
97
134
  }, [observable]);
98
135
 
99
- let status: ObservableStatus<T>['status'];
100
-
101
- if (hasError) {
102
- status = 'error';
103
- } else if (observable.hasValue || hasInitialData) {
104
- status = 'success';
105
- } else {
106
- status = 'loading';
107
- }
108
-
109
- return {
110
- status,
111
- hasEmitted: observable.hasValue || hasInitialData,
112
- isComplete: isComplete,
113
- data: latest,
114
- error: observable.ourError,
115
- firstValuePromise: observable.firstEmission,
116
- };
136
+ return status;
117
137
  }
@@ -1,10 +0,0 @@
1
- import { Observable } from 'rxjs';
2
- import type { RemoteConfig, Value as RemoteConfigValue } from 'firebase/remote-config';
3
- export declare type AllParameters = {
4
- [key: string]: RemoteConfigValue;
5
- };
6
- export declare function getValue(remoteConfig: RemoteConfig, key: string): Observable<RemoteConfigValue>;
7
- export declare function getString(remoteConfig: RemoteConfig, key: string): Observable<string>;
8
- export declare function getNumber(remoteConfig: RemoteConfig, key: string): Observable<number>;
9
- export declare function getBoolean(remoteConfig: RemoteConfig, key: string): Observable<boolean>;
10
- export declare function getAll(remoteConfig: RemoteConfig): Observable<AllParameters>;
@@ -1,58 +0,0 @@
1
- import { Observable } from 'rxjs';
2
- import {
3
- ensureInitialized,
4
- getValue as rcGetValue,
5
- getString as rcGetString,
6
- getNumber as rcGetNumber,
7
- getBoolean as rcGetBoolean,
8
- getAll as rcGetAll,
9
- } from 'firebase/remote-config';
10
-
11
- import type { RemoteConfig, Value as RemoteConfigValue } from 'firebase/remote-config';
12
-
13
- export type AllParameters = {
14
- [key: string]: RemoteConfigValue;
15
- };
16
-
17
- interface ParameterSettings<T> {
18
- remoteConfig: RemoteConfig;
19
- key: string;
20
- getter: (key: string) => T;
21
- }
22
-
23
- // TODO(davideast): Replace with RxFire functions when they land
24
- function parameter$<T>({ remoteConfig, key, getter }: ParameterSettings<T>): Observable<T> {
25
- return new Observable((subscriber) => {
26
- ensureInitialized(remoteConfig).then(() => {
27
- // 'this' for the getter loses context in the next()
28
- // call, so it needs to be bound.
29
- subscriber.next(getter.bind(remoteConfig)(key));
30
- });
31
- });
32
- }
33
-
34
- export function getValue(remoteConfig: RemoteConfig, key: string) {
35
- const getter = () => rcGetValue(remoteConfig, key);
36
- return parameter$({ remoteConfig, key, getter });
37
- }
38
-
39
- export function getString(remoteConfig: RemoteConfig, key: string) {
40
- const getter = () => rcGetString(remoteConfig, key);
41
- return parameter$<string>({ remoteConfig, key, getter });
42
- }
43
-
44
- export function getNumber(remoteConfig: RemoteConfig, key: string) {
45
- const getter = () => rcGetNumber(remoteConfig, key);
46
- return parameter$<number>({ remoteConfig, key, getter });
47
- }
48
-
49
- export function getBoolean(remoteConfig: RemoteConfig, key: string) {
50
- const getter = () => rcGetBoolean(remoteConfig, key);
51
- return parameter$<boolean>({ remoteConfig, key, getter });
52
- }
53
-
54
- export function getAll(remoteConfig: RemoteConfig) {
55
- const getter = () => rcGetAll(remoteConfig);
56
- // No key is needed for getAll()
57
- return parameter$<AllParameters>({ remoteConfig, key: '', getter });
58
- }