← back to Watches

src/hooks/useAutoRefresh.js

104 lines

import { useState, useEffect, useCallback, useRef } from 'react';

/**
 * useAutoRefresh - Custom hook for automatic data polling with visibility awareness
 * @param {function} refreshFn - Function to call for refreshing data (should return a Promise)
 * @param {number} interval - Polling interval in milliseconds (default: 5 minutes)
 * @param {boolean} enabled - Whether auto-refresh is enabled (default: true)
 * @returns {Object} { isRefreshing, lastRefresh, refresh, error }
 */
export default function useAutoRefresh(refreshFn, interval = 5 * 60 * 1000, enabled = true) {
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [lastRefresh, setLastRefresh] = useState(null);
  const [error, setError] = useState(null);

  const intervalRef = useRef(null);
  const refreshFnRef = useRef(refreshFn);
  const isVisibleRef = useRef(true);

  // Keep refreshFn ref updated
  useEffect(() => {
    refreshFnRef.current = refreshFn;
  }, [refreshFn]);

  // Manual refresh function
  const refresh = useCallback(async () => {
    if (isRefreshing) return;

    setIsRefreshing(true);
    setError(null);

    try {
      if (refreshFnRef.current) {
        await refreshFnRef.current();
      }
      setLastRefresh(Date.now());
    } catch (err) {
      console.error('Auto-refresh error:', err);
      setError(err);
    } finally {
      setIsRefreshing(false);
    }
  }, [isRefreshing]);

  // Handle visibility change
  useEffect(() => {
    const handleVisibilityChange = () => {
      isVisibleRef.current = document.visibilityState === 'visible';

      if (isVisibleRef.current && enabled) {
        // Tab became visible - check if we need to refresh
        const timeSinceLastRefresh = lastRefresh ? Date.now() - lastRefresh : Infinity;

        if (timeSinceLastRefresh >= interval) {
          refresh();
        }
      }
    };

    document.addEventListener('visibilitychange', handleVisibilityChange);

    return () => {
      document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, [enabled, interval, lastRefresh, refresh]);

  // Set up polling interval
  useEffect(() => {
    if (!enabled) {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
        intervalRef.current = null;
      }
      return;
    }

    // Initial refresh
    if (!lastRefresh) {
      refresh();
    }

    // Set up interval
    intervalRef.current = setInterval(() => {
      // Only refresh if tab is visible
      if (isVisibleRef.current) {
        refresh();
      }
    }, interval);

    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
        intervalRef.current = null;
      }
    };
  }, [enabled, interval, refresh, lastRefresh]);

  return {
    isRefreshing,
    lastRefresh,
    refresh,
    error
  };
}