or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

development-tools.mdevent-handling.mdindex.mdlifecycle-management.mdreference-management.mdstate-management.mdtiming-utilities.mdutility-hooks.md
tile.json

timing-utilities.mddocs/

Timing Utilities

Safe wrappers for setTimeout and setInterval with automatic cleanup on component unmount.

Capabilities

useSetTimeout

Returns a wrapper function for setTimeout which automatically handles disposal when the component unmounts. This prevents memory leaks and errors from timeouts that fire after component unmount.

/**
 * Returns a wrapper function for setTimeout which automatically handles disposal.
 * @returns Object with setTimeout and clearTimeout methods that handle cleanup
 */
function useSetTimeout(): UseSetTimeoutReturnType;

interface UseSetTimeoutReturnType {
  /** Safe setTimeout that auto-cleans on unmount */
  setTimeout: (callback: () => void, duration: number) => number;
  /** Clear a specific timeout */
  clearTimeout: (id: number) => void;
}

Usage Examples:

import { useSetTimeout } from "@fluentui/react-hooks";

function DelayedMessageComponent() {
  const [message, setMessage] = useState('');
  const { setTimeout, clearTimeout } = useSetTimeout();

  const showDelayedMessage = () => {
    setMessage('Loading...');
    
    const timeoutId = setTimeout(() => {
      setMessage('Message loaded after 2 seconds!');
    }, 2000);

    // Optionally clear the timeout early
    // clearTimeout(timeoutId);
  };

  return (
    <div>
      <button onClick={showDelayedMessage}>Show Delayed Message</button>
      <div>{message}</div>
    </div>
  );
}

// Auto-save functionality
function AutoSaveForm({ onSave, data }) {
  const [isDirty, setIsDirty] = useState(false);
  const { setTimeout, clearTimeout } = useSetTimeout();
  const saveTimeoutRef = useRef<number>();

  const handleChange = (newData) => {
    setIsDirty(true);
    
    // Clear existing save timeout
    if (saveTimeoutRef.current) {
      clearTimeout(saveTimeoutRef.current);
    }

    // Set new save timeout
    saveTimeoutRef.current = setTimeout(() => {
      onSave(newData);
      setIsDirty(false);
    }, 1000); // Save after 1 second of no changes
  };

  return (
    <div>
      <input onChange={(e) => handleChange(e.target.value)} />
      {isDirty && <span>Unsaved changes...</span>}
    </div>
  );
}

// Notification with auto-dismiss
function NotificationComponent({ message, duration = 5000 }) {
  const [isVisible, setIsVisible] = useState(true);
  const { setTimeout } = useSetTimeout();

  useEffect(() => {
    if (isVisible && duration > 0) {
      setTimeout(() => {
        setIsVisible(false);
      }, duration);
    }
  }, [isVisible, duration, setTimeout]);

  if (!isVisible) return null;

  return (
    <div className="notification">
      {message}
      <button onClick={() => setIsVisible(false)}>×</button>
    </div>
  );
}

useSetInterval

Returns a wrapper function for setInterval which automatically handles disposal when the component unmounts. Prevents memory leaks from intervals that continue after component unmount.

/**
 * Returns a wrapper function for setInterval which automatically handles disposal.
 * @returns Object with setInterval and clearInterval methods that handle cleanup
 */
function useSetInterval(): UseSetIntervalReturnType;

interface UseSetIntervalReturnType {
  /** Safe setInterval that auto-cleans on unmount */
  setInterval: (callback: () => void, duration: number) => number;
  /** Clear a specific interval */
  clearInterval: (id: number) => void;
}

Usage Examples:

import { useSetInterval } from "@fluentui/react-hooks";

// Real-time clock
function ClockComponent() {
  const [time, setTime] = useState(new Date());
  const { setInterval } = useSetInterval();

  useEffect(() => {
    const intervalId = setInterval(() => {
      setTime(new Date());
    }, 1000);

    // Cleanup is handled automatically by useSetInterval
    return () => {
      // Optional: manual cleanup if needed before unmount
    };
  }, [setInterval]);

  return (
    <div>
      Current time: {time.toLocaleTimeString()}
    </div>
  );
}

// Progress bar animation
function ProgressBarComponent({ duration = 10000 }) {
  const [progress, setProgress] = useState(0);
  const { setInterval, clearInterval } = useSetInterval();
  const intervalRef = useRef<number>();

  const startProgress = () => {
    setProgress(0);
    
    intervalRef.current = setInterval(() => {
      setProgress(prev => {
        const newProgress = prev + 1;
        
        // Stop at 100%
        if (newProgress >= 100) {
          clearInterval(intervalRef.current!);
        }
        
        return Math.min(newProgress, 100);
      });
    }, duration / 100);
  };

  const stopProgress = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }
  };

  return (
    <div>
      <div>Progress: {progress}%</div>
      <div 
        style={{ 
          width: '200px', 
          height: '20px', 
          backgroundColor: '#f0f0f0',
          border: '1px solid #ccc'
        }}
      >
        <div 
          style={{ 
            width: `${progress}%`, 
            height: '100%', 
            backgroundColor: '#007acc',
            transition: 'width 0.1s ease'
          }} 
        />
      </div>
      <button onClick={startProgress}>Start</button>
      <button onClick={stopProgress}>Stop</button>
    </div>
  );
}

// Polling for data updates
function DataPollingComponent({ url, pollInterval = 30000 }) {
  const [data, setData] = useState(null);
  const [isPolling, setIsPolling] = useState(false);
  const { setInterval, clearInterval } = useSetInterval();
  const pollIntervalRef = useRef<number>();

  const fetchData = async () => {
    try {
      const response = await fetch(url);
      const newData = await response.json();
      setData(newData);
    } catch (error) {
      console.error('Failed to fetch data:', error);
    }
  };

  const startPolling = () => {
    setIsPolling(true);
    fetchData(); // Initial fetch

    pollIntervalRef.current = setInterval(() => {
      fetchData();
    }, pollInterval);
  };

  const stopPolling = () => {
    setIsPolling(false);
    if (pollIntervalRef.current) {
      clearInterval(pollIntervalRef.current);
    }
  };

  return (
    <div>
      <div>
        <button onClick={startPolling} disabled={isPolling}>
          Start Polling
        </button>
        <button onClick={stopPolling} disabled={!isPolling}>
          Stop Polling
        </button>
      </div>
      
      <div>Status: {isPolling ? 'Polling...' : 'Stopped'}</div>
      
      {data && (
        <div>
          <h3>Latest Data:</h3>
          <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

// Heartbeat / Keep-alive mechanism
function HeartbeatComponent({ onHeartbeat, interval = 60000 }) {
  const [isActive, setIsActive] = useState(false);
  const [heartbeatCount, setHeartbeatCount] = useState(0);
  const { setInterval, clearInterval } = useSetInterval();
  const heartbeatRef = useRef<number>();

  const startHeartbeat = () => {
    setIsActive(true);
    setHeartbeatCount(0);

    heartbeatRef.current = setInterval(() => {
      setHeartbeatCount(prev => prev + 1);
      onHeartbeat();
    }, interval);
  };

  const stopHeartbeat = () => {
    setIsActive(false);
    if (heartbeatRef.current) {
      clearInterval(heartbeatRef.current);
    }
  };

  return (
    <div>
      <div>
        Heartbeat: {isActive ? 'Active' : 'Inactive'}
      </div>
      <div>
        Count: {heartbeatCount}
      </div>
      <button onClick={startHeartbeat} disabled={isActive}>
        Start Heartbeat
      </button>
      <button onClick={stopHeartbeat} disabled={!isActive}>
        Stop Heartbeat
      </button>
    </div>
  );
}