11/* eslint-disable react/prefer-stateless-function */
22
3- import React , { useState , useEffect , useRef } from 'react'
3+ import React , { useState , useEffect , useRef , useCallback } from 'react'
44import { Meteor } from 'meteor/meteor'
55import { Mongo } from 'meteor/mongo'
66import { Tracker } from 'meteor/tracker'
@@ -13,6 +13,8 @@ const globalTrackerQueue: Array<Function> = []
1313let globalTrackerTimestamp : number | undefined = undefined
1414let globalTrackerTimeout : number | undefined = undefined
1515
16+ const SUBSCRIPTION_TIMEOUT = 1000
17+
1618/**
1719 * Delay an update to be batched with the global tracker invalidation queue
1820 */
@@ -370,6 +372,46 @@ export function useTracker<T, K extends undefined | T = undefined>(
370372 return meteorData
371373}
372374
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+
373415/**
374416 * A Meteor Subscription hook that allows using React Functional Components and the Hooks API with Meteor subscriptions.
375417 * Subscriptions will be torn down 1000ms after unmounting the component.
@@ -383,20 +425,28 @@ export function useSubscription<K extends keyof AllPubSubTypes>(
383425 sub : K ,
384426 ...args : Parameters < AllPubSubTypes [ K ] >
385427) : boolean {
386- const [ ready , setReady ] = useState < boolean > ( false )
428+ const { ready, setReady, cancelPreviousReady } = useReadyState ( )
387429
388430 useEffect ( ( ) => {
389431 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+ )
391438 return ( ) => {
392439 isReadyComp . stop ( )
393440 setTimeout ( ( ) => {
394441 subscription . stop ( )
395- } , 1000 )
442+ } , SUBSCRIPTION_TIMEOUT )
443+ cancelPreviousReady ( SUBSCRIPTION_TIMEOUT )
396444 }
397445 } , [ sub , stringifyObjects ( args ) ] )
398446
399- return ready
447+ const isReady = ready
448+
449+ return isReady
400450}
401451
402452/**
@@ -415,7 +465,7 @@ export function useSubscriptionIfEnabled<K extends keyof AllPubSubTypes>(
415465 enable : boolean ,
416466 ...args : Parameters < AllPubSubTypes [ K ] >
417467) : boolean {
418- const [ ready , setReady ] = useState < boolean > ( false )
468+ const { ready, setReady, cancelPreviousReady } = useReadyState ( )
419469
420470 useEffect ( ( ) => {
421471 if ( ! enable ) {
@@ -424,16 +474,69 @@ export function useSubscriptionIfEnabled<K extends keyof AllPubSubTypes>(
424474 }
425475
426476 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+ )
428483 return ( ) => {
429484 isReadyComp . stop ( )
430485 setTimeout ( ( ) => {
431486 subscription . stop ( )
432- } , 1000 )
487+ } , SUBSCRIPTION_TIMEOUT )
488+ cancelPreviousReady ( SUBSCRIPTION_TIMEOUT )
433489 }
434490 } , [ sub , enable , stringifyObjects ( args ) ] )
435491
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
437540}
438541
439542/**
0 commit comments