@@ -63,55 +63,75 @@ export interface ObservableStatus<T> {
6363 firstValuePromise : Promise < void > ;
6464}
6565
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+
6695export function useObservable < T = unknown > ( observableId : string , source : Observable < T > , config : ReactFireOptions = { } ) : ObservableStatus < T > {
96+ // Register the observable with the cache
6797 if ( ! observableId ) {
6898 throw new Error ( 'cannot call useObservable without an observableId' ) ;
6999 }
70100 const observable = preloadObservable ( source , observableId ) ;
71101
102+ // Suspend if suspense is enabled and no initial data exists
72103 const hasInitialData = config . hasOwnProperty ( 'initialData' ) || config . hasOwnProperty ( 'startWithValue' ) ;
73-
104+ const hasData = observable . hasValue || hasInitialData ;
74105 const suspenseEnabled = useSuspenseEnabledFromConfigAndContext ( config . suspense ) ;
75-
76- if ( suspenseEnabled === true && ! observable . hasValue && ( ! config ?. initialData ?? ! config ?. startWithValue ) ) {
106+ if ( suspenseEnabled === true && ! hasData ) {
77107 throw observable . firstEmission ;
78108 }
79109
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+
83120 React . useEffect ( ( ) => {
84121 const subscription = observable . subscribe ( {
85- next : ( v ) => {
86- setValue ( ( ) => v ) ;
122+ next : ( ) => {
123+ dispatch ( 'value' ) ;
87124 } ,
88125 error : ( e ) => {
89- setHasError ( true ) ;
126+ dispatch ( 'error' ) ;
90127 throw e ;
91128 } ,
92129 complete : ( ) => {
93- setIsComplete ( true ) ;
130+ dispatch ( 'complete' ) ;
94131 } ,
95132 } ) ;
96133 return ( ) => subscription . unsubscribe ( ) ;
97134 } , [ observable ] ) ;
98135
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 ;
117137}
0 commit comments