Safe wrappers for setTimeout and setInterval with automatic cleanup on component unmount.
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>
);
}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>
);
}