| 93 | } |
| 94 | |
| 95 | export function useObservable<T = unknown>(observableId: string, source: Observable<T>, config: ReactFireOptions = {}): ObservableStatus<T> { |
| 96 | // Register the observable with the cache |
| 97 | if (!observableId) { |
| 98 | throw new Error('cannot call useObservable without an observableId'); |
| 99 | } |
| 100 | const observable = preloadObservable(source, observableId); |
| 101 | |
| 102 | // Suspend if suspense is enabled and no initial data exists |
| 103 | const hasInitialData = config.hasOwnProperty('initialData') || config.hasOwnProperty('startWithValue'); |
| 104 | const hasData = observable.hasValue || hasInitialData; |
| 105 | const suspenseEnabled = useSuspenseEnabledFromConfigAndContext(config.suspense); |
| 106 | if (suspenseEnabled === true && !hasData) { |
| 107 | throw observable.firstEmission; |
| 108 | } |
| 109 | |
| 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 | |
| 120 | React.useEffect(() => { |
| 121 | const subscription = observable.subscribe({ |
| 122 | next: () => { |
| 123 | dispatch('value'); |
| 124 | }, |
| 125 | error: (e) => { |
| 126 | dispatch('error'); |
| 127 | throw e; |
| 128 | }, |
| 129 | complete: () => { |
| 130 | dispatch('complete'); |
| 131 | }, |
| 132 | }); |
| 133 | return () => subscription.unsubscribe(); |
| 134 | }, [observable]); |
| 135 | |
| 136 | return status; |
| 137 | } |