@@ -19,10 +19,14 @@ import {
1919 makeObservable ,
2020 observable ,
2121 runInAction ,
22+ onBecomeUnobserved ,
23+ onBecomeObserved ,
2224} from 'mobx' ;
2325
2426import {
2527 InfiniteQueryConfig ,
28+ InfiniteQueryDoneListener ,
29+ InfiniteQueryErrorListener ,
2630 InfiniteQueryFlattenConfig ,
2731 InfiniteQueryInvalidateParams ,
2832 InfiniteQueryOptions ,
@@ -32,6 +36,8 @@ import {
3236} from './inifinite-query.types' ;
3337import { AnyQueryClient , QueryClientHooks } from './query-client.types' ;
3438
39+ const enableHolder = ( ) => false ;
40+
3541export class InfiniteQuery <
3642 TQueryFnData = unknown ,
3743 TError = DefaultError ,
@@ -40,7 +46,7 @@ export class InfiniteQuery<
4046 TQueryKey extends QueryKey = QueryKey ,
4147> implements Disposable
4248{
43- protected abortController : AbortController ;
49+ protected abortController : LinkedAbortController ;
4450 protected queryClient : AnyQueryClient ;
4551
4652 protected _result : InfiniteQueryObserverResult < TData , TError > ;
@@ -68,9 +74,9 @@ export class InfiniteQuery<
6874 TPageParam
6975 > ;
7076
71- isResultRequsted : boolean ;
72-
7377 private isEnabledOnResultDemand : boolean ;
78+ private isResultRequsted : boolean ;
79+ private isLazy ?: boolean ;
7480
7581 /**
7682 * This parameter is responsible for holding the enabled value,
@@ -85,6 +91,8 @@ export class InfiniteQuery<
8591 > [ 'enabled' ] ;
8692 private _observerSubscription ?: VoidFunction ;
8793 private hooks ?: QueryClientHooks ;
94+ private errorListeners : InfiniteQueryErrorListener < TError > [ ] ;
95+ private doneListeners : InfiniteQueryDoneListener < TData > [ ] ;
8896
8997 constructor (
9098 config : InfiniteQueryConfig <
@@ -147,12 +155,20 @@ export class InfiniteQuery<
147155 this . _result = undefined as any ;
148156 this . isResultRequsted = false ;
149157 this . isEnabledOnResultDemand = config . enableOnDemand ?? false ;
158+ this . errorListeners = [ ] ;
159+ this . doneListeners = [ ] ;
150160 this . hooks =
151161 'hooks' in this . queryClient ? this . queryClient . hooks : undefined ;
162+ this . isLazy = this . config . lazy ;
152163
153- if ( 'queryFeatures' in queryClient && config . enableOnDemand == null ) {
154- this . isEnabledOnResultDemand =
155- queryClient . queryFeatures . enableOnDemand ?? false ;
164+ if ( 'queryFeatures' in queryClient ) {
165+ if ( this . config . lazy === undefined ) {
166+ this . isLazy = queryClient . queryFeatures . lazy ?? false ;
167+ }
168+ if ( config . enableOnDemand === undefined ) {
169+ this . isEnabledOnResultDemand =
170+ queryClient . queryFeatures . enableOnDemand ?? false ;
171+ }
156172 }
157173
158174 observable . deep ( this , '_result' ) ;
@@ -164,10 +180,11 @@ export class InfiniteQuery<
164180
165181 makeObservable ( this ) ;
166182
167- this . options = this . queryClient . defaultQueryOptions ( {
168- ...restOptions ,
169- ...getDynamicOptions ?.( this ) ,
170- } as any ) as InfiniteQueryOptions <
183+ const isQueryKeyDynamic = typeof queryKeyOrDynamicQueryKey === 'function' ;
184+
185+ this . options = this . queryClient . defaultQueryOptions (
186+ restOptions as any ,
187+ ) as InfiniteQueryOptions <
171188 TQueryFnData ,
172189 TError ,
173190 TPageParam ,
@@ -177,24 +194,24 @@ export class InfiniteQuery<
177194
178195 this . options . structuralSharing = this . options . structuralSharing ?? false ;
179196
180- this . processOptions ( this . options ) ;
197+ const getAllDynamicOptions =
198+ getDynamicOptions || isQueryKeyDynamic
199+ ? ( ) => {
200+ const freshDynamicOptions = {
201+ ...getDynamicOptions ?.( this ) ,
202+ } ;
181203
182- if ( typeof queryKeyOrDynamicQueryKey === 'function' ) {
183- this . options . queryKey = queryKeyOrDynamicQueryKey ( ) ;
184-
185- reaction (
186- ( ) => queryKeyOrDynamicQueryKey ( ) ,
187- ( queryKey ) => {
188- this . update ( {
189- queryKey,
190- } ) ;
191- } ,
192- {
193- signal : this . abortController . signal ,
194- delay : this . config . dynamicOptionsUpdateDelay ,
195- } ,
196- ) ;
197- } else {
204+ if ( isQueryKeyDynamic ) {
205+ freshDynamicOptions . queryKey = queryKeyOrDynamicQueryKey ( ) ;
206+ }
207+
208+ return freshDynamicOptions ;
209+ }
210+ : undefined ;
211+
212+ if ( getAllDynamicOptions ) {
213+ Object . assign ( this . options , getAllDynamicOptions ( ) ) ;
214+ } else if ( ! isQueryKeyDynamic ) {
198215 this . options . queryKey =
199216 queryKeyOrDynamicQueryKey ?? this . options . queryKey ?? [ ] ;
200217 }
@@ -205,47 +222,81 @@ export class InfiniteQuery<
205222 queryClient . getDefaultOptions ( ) . queries ?. notifyOnChangeProps ??
206223 'all' ;
207224
225+ this . processOptions ( this . options ) ;
226+
208227 // @ts -expect-error
209228 this . queryObserver = new InfiniteQueryObserver ( queryClient , this . options ) ;
210229
211230 // @ts -expect-error
212231 this . updateResult ( this . queryObserver . getOptimisticResult ( this . options ) ) ;
213232
214- this . _observerSubscription = this . queryObserver . subscribe (
215- this . updateResult ,
216- ) ;
233+ if ( this . isLazy ) {
234+ let dynamicOptionsDisposeFn : VoidFunction | undefined ;
217235
218- if ( getDynamicOptions ) {
219- reaction ( ( ) => getDynamicOptions ( this ) , this . update , {
220- signal : this . abortController . signal ,
221- delay : this . config . dynamicOptionsUpdateDelay ,
236+ onBecomeObserved ( this , '_result' , ( ) => {
237+ if ( ! this . _observerSubscription ) {
238+ if ( getAllDynamicOptions ) {
239+ this . update ( getAllDynamicOptions ( ) ) ;
240+ }
241+ this . _observerSubscription = this . queryObserver . subscribe (
242+ this . updateResult ,
243+ ) ;
244+ if ( getAllDynamicOptions ) {
245+ dynamicOptionsDisposeFn = reaction (
246+ getAllDynamicOptions ,
247+ this . update ,
248+ {
249+ delay : this . config . dynamicOptionsUpdateDelay ,
250+ signal : config . abortSignal ,
251+ fireImmediately : true ,
252+ } ,
253+ ) ;
254+ }
255+ }
222256 } ) ;
223- }
224257
225- if ( this . isEnabledOnResultDemand ) {
226- reaction (
227- ( ) => this . isResultRequsted ,
228- ( isRequested ) => {
229- if ( isRequested ) {
230- this . update ( getDynamicOptions ?.( this ) ?? { } ) ;
231- }
232- } ,
233- {
258+ const cleanup = ( ) => {
259+ if ( this . _observerSubscription ) {
260+ dynamicOptionsDisposeFn ?.( ) ;
261+ this . _observerSubscription ( ) ;
262+ this . _observerSubscription = undefined ;
263+ dynamicOptionsDisposeFn = undefined ;
264+ config . abortSignal ?. removeEventListener ( 'abort' , cleanup ) ;
265+ }
266+ } ;
267+
268+ onBecomeUnobserved ( this , '_result' , cleanup ) ;
269+ config . abortSignal ?. addEventListener ( 'abort' , cleanup ) ;
270+ } else {
271+ if ( isQueryKeyDynamic ) {
272+ reaction (
273+ queryKeyOrDynamicQueryKey ,
274+ ( queryKey ) => this . update ( { queryKey } ) ,
275+ {
276+ signal : this . abortController . signal ,
277+ delay : this . config . dynamicOptionsUpdateDelay ,
278+ } ,
279+ ) ;
280+ }
281+ if ( getDynamicOptions ) {
282+ reaction ( ( ) => getDynamicOptions ( this ) , this . update , {
234283 signal : this . abortController . signal ,
235- fireImmediately : true ,
236- } ,
284+ delay : this . config . dynamicOptionsUpdateDelay ,
285+ } ) ;
286+ }
287+ this . _observerSubscription = this . queryObserver . subscribe (
288+ this . updateResult ,
237289 ) ;
290+ this . abortController . signal . addEventListener ( 'abort' , this . handleAbort ) ;
238291 }
239292
240293 if ( config . onDone ) {
241- this . onDone ( config . onDone ) ;
294+ this . doneListeners . push ( config . onDone ) ;
242295 }
243296 if ( config . onError ) {
244- this . onError ( config . onError ) ;
297+ this . errorListeners . push ( config . onError ) ;
245298 }
246299
247- this . abortController . signal . addEventListener ( 'abort' , this . handleAbort ) ;
248-
249300 this . config . onInit ?.( this ) ;
250301 this . hooks ?. onInfiniteQueryInit ?.( this ) ;
251302 }
@@ -316,12 +367,14 @@ export class InfiniteQuery<
316367
317368 // @ts -expect-error
318369 this . queryObserver . setOptions ( this . options ) ;
370+
371+ if ( this . isLazy ) {
372+ this . updateResult ( this . queryObserver . getCurrentResult ( ) ) ;
373+ }
319374 }
320375
321376 private isEnableHolded = false ;
322377
323- private enableHolder = ( ) => false ;
324-
325378 private processOptions = (
326379 options : InfiniteQueryOptions <
327380 TQueryFnData ,
@@ -338,40 +391,47 @@ export class InfiniteQuery<
338391 // to do this, we hold the original value of the enabled option
339392 // and set enabled to false until the user requests the result (this.isResultRequsted)
340393 if ( this . isEnabledOnResultDemand ) {
341- if ( this . isEnableHolded && options . enabled !== this . enableHolder ) {
394+ if ( this . isEnableHolded && options . enabled !== enableHolder ) {
342395 this . holdedEnabledOption = options . enabled ;
343396 }
344397
345398 if ( this . isResultRequsted ) {
346399 if ( this . isEnableHolded ) {
347400 options . enabled =
348- this . holdedEnabledOption === this . enableHolder
401+ this . holdedEnabledOption === enableHolder
349402 ? undefined
350403 : this . holdedEnabledOption ;
351404 this . isEnableHolded = false ;
352405 }
353406 } else {
354407 this . isEnableHolded = true ;
355408 this . holdedEnabledOption = options . enabled ;
356- options . enabled = this . enableHolder ;
409+ options . enabled = enableHolder ;
357410 }
358411 }
359412 } ;
360413
361414 public get result ( ) {
362- if ( ! this . isResultRequsted ) {
415+ if ( this . isEnabledOnResultDemand && ! this . isResultRequsted ) {
363416 runInAction ( ( ) => {
364417 this . isResultRequsted = true ;
365418 } ) ;
419+ this . update ( { } ) ;
366420 }
367421 return this . _result ;
368422 }
369423
370424 /**
371425 * Modify this result so it matches the tanstack query result.
372426 */
373- private updateResult ( nextResult : InfiniteQueryObserverResult < TData , TError > ) {
374- this . _result = nextResult || { } ;
427+ private updateResult ( result : InfiniteQueryObserverResult < TData , TError > ) {
428+ this . _result = result || { } ;
429+
430+ if ( result . isSuccess && ! result . error && result . fetchStatus === 'idle' ) {
431+ this . doneListeners . forEach ( ( fn ) => fn ( result . data ! , void 0 ) ) ;
432+ } else if ( result . error ) {
433+ this . errorListeners . forEach ( ( fn ) => fn ( result . error ! , void 0 ) ) ;
434+ }
375435 }
376436
377437 async refetch ( options ?: RefetchOptions ) {
@@ -408,35 +468,12 @@ export class InfiniteQuery<
408468 } as any ) ;
409469 }
410470
411- onDone ( onDoneCallback : ( data : TData , payload : void ) => void ) : void {
412- reaction (
413- ( ) => {
414- const { error, isSuccess, fetchStatus } = this . _result ;
415- return isSuccess && ! error && fetchStatus === 'idle' ;
416- } ,
417- ( isDone ) => {
418- if ( isDone ) {
419- onDoneCallback ( this . _result . data ! , void 0 ) ;
420- }
421- } ,
422- {
423- signal : this . abortController . signal ,
424- } ,
425- ) ;
471+ onDone ( doneListener : InfiniteQueryDoneListener < TData > ) : void {
472+ this . doneListeners . push ( doneListener ) ;
426473 }
427474
428- onError ( onErrorCallback : ( error : TError , payload : void ) => void ) : void {
429- reaction (
430- ( ) => this . _result . error ,
431- ( error ) => {
432- if ( error ) {
433- onErrorCallback ( error , void 0 ) ;
434- }
435- } ,
436- {
437- signal : this . abortController . signal ,
438- } ,
439- ) ;
475+ onError ( errorListener : InfiniteQueryErrorListener < TError > ) : void {
476+ this . errorListeners . push ( errorListener ) ;
440477 }
441478
442479 async start ( {
@@ -457,6 +494,9 @@ export class InfiniteQuery<
457494 protected handleAbort = ( ) => {
458495 this . _observerSubscription ?.( ) ;
459496
497+ this . doneListeners = [ ] ;
498+ this . errorListeners = [ ] ;
499+
460500 this . queryObserver . destroy ( ) ;
461501 this . isResultRequsted = false ;
462502
0 commit comments