1
1
/* eslint-disable react/prefer-stateless-function */
2
2
3
- import React , { useState , useEffect , useRef } from 'react'
3
+ import React , { useState , useEffect , useRef , useCallback } from 'react'
4
4
import { Meteor } from 'meteor/meteor'
5
5
import { Mongo } from 'meteor/mongo'
6
6
import { Tracker } from 'meteor/tracker'
@@ -13,6 +13,8 @@ const globalTrackerQueue: Array<Function> = []
13
13
let globalTrackerTimestamp : number | undefined = undefined
14
14
let globalTrackerTimeout : number | undefined = undefined
15
15
16
+ const SUBSCRIPTION_TIMEOUT = 1000
17
+
16
18
/**
17
19
* Delay an update to be batched with the global tracker invalidation queue
18
20
*/
@@ -370,6 +372,46 @@ export function useTracker<T, K extends undefined | T = undefined>(
370
372
return meteorData
371
373
}
372
374
375
+ function useReadyState ( ) : {
376
+ ready : boolean
377
+ setReady : ( value : boolean ) => void
378
+ cancelPreviousReady : ( timeout : number ) => void
379
+ } {
380
+ const [ ready , setReady ] = useState ( false )
381
+ const [ prevReady , setPrevReady ] = useState ( false )
382
+ const prevReadyTimeoutRef = useRef < number | null > ( null )
383
+
384
+ const setIsReady = useCallback (
385
+ ( value : boolean ) => {
386
+ setReady ( value )
387
+
388
+ if ( value ) {
389
+ setPrevReady ( true )
390
+ if ( prevReadyTimeoutRef . current !== null ) {
391
+ window . clearTimeout ( prevReadyTimeoutRef . current )
392
+ prevReadyTimeoutRef . current = null
393
+ }
394
+ }
395
+ } ,
396
+ [ setReady , setPrevReady ]
397
+ )
398
+
399
+ const cancelPrevReady = useCallback (
400
+ ( timeout : number ) => {
401
+ prevReadyTimeoutRef . current = window . setTimeout ( ( ) => {
402
+ setPrevReady ( false )
403
+ } , timeout )
404
+ } ,
405
+ [ prevReadyTimeoutRef ]
406
+ )
407
+
408
+ return {
409
+ ready : ready || prevReady ,
410
+ setReady : setIsReady ,
411
+ cancelPreviousReady : cancelPrevReady ,
412
+ }
413
+ }
414
+
373
415
/**
374
416
* A Meteor Subscription hook that allows using React Functional Components and the Hooks API with Meteor subscriptions.
375
417
* Subscriptions will be torn down 1000ms after unmounting the component.
@@ -383,20 +425,28 @@ export function useSubscription<K extends keyof AllPubSubTypes>(
383
425
sub : K ,
384
426
...args : Parameters < AllPubSubTypes [ K ] >
385
427
) : boolean {
386
- const [ ready , setReady ] = useState < boolean > ( false )
428
+ const { ready, setReady, cancelPreviousReady } = useReadyState ( )
387
429
388
430
useEffect ( ( ) => {
389
431
const subscription = Tracker . nonreactive ( ( ) => meteorSubscribe ( sub , ...args ) )
390
- const isReadyComp = Tracker . nonreactive ( ( ) => Tracker . autorun ( ( ) => setReady ( subscription . ready ( ) ) ) )
432
+ const isReadyComp = Tracker . nonreactive ( ( ) =>
433
+ Tracker . autorun ( ( ) => {
434
+ const isNowReady = subscription . ready ( )
435
+ setReady ( isNowReady )
436
+ } )
437
+ )
391
438
return ( ) => {
392
439
isReadyComp . stop ( )
393
440
setTimeout ( ( ) => {
394
441
subscription . stop ( )
395
- } , 1000 )
442
+ } , SUBSCRIPTION_TIMEOUT )
443
+ cancelPreviousReady ( SUBSCRIPTION_TIMEOUT )
396
444
}
397
445
} , [ sub , stringifyObjects ( args ) ] )
398
446
399
- return ready
447
+ const isReady = ready
448
+
449
+ return isReady
400
450
}
401
451
402
452
/**
@@ -415,7 +465,7 @@ export function useSubscriptionIfEnabled<K extends keyof AllPubSubTypes>(
415
465
enable : boolean ,
416
466
...args : Parameters < AllPubSubTypes [ K ] >
417
467
) : boolean {
418
- const [ ready , setReady ] = useState < boolean > ( false )
468
+ const { ready, setReady, cancelPreviousReady } = useReadyState ( )
419
469
420
470
useEffect ( ( ) => {
421
471
if ( ! enable ) {
@@ -424,16 +474,69 @@ export function useSubscriptionIfEnabled<K extends keyof AllPubSubTypes>(
424
474
}
425
475
426
476
const subscription = Tracker . nonreactive ( ( ) => meteorSubscribe ( sub , ...args ) )
427
- const isReadyComp = Tracker . nonreactive ( ( ) => Tracker . autorun ( ( ) => setReady ( subscription . ready ( ) ) ) )
477
+ const isReadyComp = Tracker . nonreactive ( ( ) =>
478
+ Tracker . autorun ( ( ) => {
479
+ const isNowReady = subscription . ready ( )
480
+ setReady ( isNowReady )
481
+ } )
482
+ )
428
483
return ( ) => {
429
484
isReadyComp . stop ( )
430
485
setTimeout ( ( ) => {
431
486
subscription . stop ( )
432
- } , 1000 )
487
+ } , SUBSCRIPTION_TIMEOUT )
488
+ cancelPreviousReady ( SUBSCRIPTION_TIMEOUT )
433
489
}
434
490
} , [ sub , enable , stringifyObjects ( args ) ] )
435
491
436
- return ! enable || ready
492
+ const isReady = ! enable || ready
493
+
494
+ return isReady
495
+ }
496
+
497
+ /**
498
+ * A Meteor Subscription hook that allows using React Functional Components and the Hooks API with Meteor subscriptions.
499
+ * Subscriptions will be torn down 1000ms after unmounting the component.
500
+ * If the subscription is not enabled, the subscription will not be created, and the ready state will always be true.
501
+ *
502
+ * @export
503
+ * @param {PubSub } sub The subscription to be subscribed to
504
+ * @param {boolean } enable Whether the subscription is enabled
505
+ * @param {...any[] } args A list of arugments for the subscription. This is used for optimizing the subscription across
506
+ * renders so that it isn't torn down and created for every render.
507
+ */
508
+ export function useSubscriptionIfEnabledReadyOnce < K extends keyof AllPubSubTypes > (
509
+ sub : K ,
510
+ enable : boolean ,
511
+ ...args : Parameters < AllPubSubTypes [ K ] >
512
+ ) : boolean {
513
+ const { ready, setReady, cancelPreviousReady } = useReadyState ( )
514
+
515
+ useEffect ( ( ) => {
516
+ if ( ! enable ) {
517
+ setReady ( false )
518
+ return
519
+ }
520
+
521
+ const subscription = Tracker . nonreactive ( ( ) => meteorSubscribe ( sub , ...args ) )
522
+ const isReadyComp = Tracker . nonreactive ( ( ) =>
523
+ Tracker . autorun ( ( ) => {
524
+ const isNowReady = subscription . ready ( )
525
+ if ( isNowReady ) setReady ( true )
526
+ } )
527
+ )
528
+ return ( ) => {
529
+ isReadyComp . stop ( )
530
+ setTimeout ( ( ) => {
531
+ subscription . stop ( )
532
+ } , SUBSCRIPTION_TIMEOUT )
533
+ cancelPreviousReady ( SUBSCRIPTION_TIMEOUT )
534
+ }
535
+ } , [ sub , enable , stringifyObjects ( args ) ] )
536
+
537
+ const isReady = ! enable || ready
538
+
539
+ return isReady
437
540
}
438
541
439
542
/**
0 commit comments