-
Notifications
You must be signed in to change notification settings - Fork 0
useTabVisibility
The useTabVisibility hook tracks whether the browser tab/document is currently active in real-time. Unlike basic document visibility listeners, it handles multi-monitor window blurring, mobile app freeze/resume (BFCache), and avoids false-positive "user left" triggers during initial hydration.
The simplest way to use useTabVisibility is to monitor the boolean isActive status or respond to basic activation/deactivation.
import { useTabVisibility } from 'react-hook-lab';
function ActiveTracker() {
const { isActive } = useTabVisibility();
return (
<div>
<p>Status: {isActive ? "Active" : "Inactive"}</p>
</div>
);
}In real-world applications, you often want to pause resource-intensive tasks (e.g. video playback, animations, or WebSocket polling) when the user looks away or switches tabs.
import { useRef } from 'react';
import { useTabVisibility } from 'react-hook-lab';
function VideoPlayer() {
const videoRef = useRef<HTMLVideoElement>(null);
const { isActive, isVisible, isFocused, wasActive } = useTabVisibility({
requireWindowFocus: true, // Default: true. User must be focused on this window
onActivate: () => {
console.log("Welcome back!");
videoRef.current?.play();
},
onDeactivate: () => {
console.log("User moved away from the tab");
videoRef.current?.pause();
},
});
return (
<div>
<video ref={videoRef} src="/media/sample.mp4" controls />
<div>
<span>{isActive ? "🟢 Active" : "🔴 Inactive"}</span>
{isVisible && !isFocused && (
<small> (Visible on screen, but unfocused in another app)</small>
)}
</div>
</div>
);
}For security-sensitive apps, idle session timeouts, or detailed telemetry, use timestamps (lastActiveAt, lastInactiveAt) and wasActive to measure duration spent away without hydration false-positives.
import { useEffect } from 'react';
import { useTabVisibility } from 'react-hook-lab';
function SecureSessionMonitor() {
const {
isActive,
wasActive,
isVisible,
isFocused,
lastActiveAt,
lastInactiveAt,
} = useTabVisibility({
requireWindowFocus: true,
initialActive: true,
});
useEffect(() => {
// wasActive is undefined until a real user transition occurs!
if (!isActive && wasActive && lastInactiveAt) {
console.log(`User left session at timestamp ${lastInactiveAt}`);
} else if (isActive && wasActive === false && lastActiveAt && lastInactiveAt) {
const awayDurationSeconds = Math.round((lastActiveAt - lastInactiveAt) / 1000);
console.log(`User returned after being away for ${awayDurationSeconds}s`);
if (awayDurationSeconds > 300) {
// Prompt for re-authentication if away for more than 5 minutes
promptReauth();
}
}
}, [isActive, wasActive, lastActiveAt, lastInactiveAt]);
return (
<div>
<h3>Security Gateway</h3>
<p>Window active: {isActive ? "Yes" : "Locked / Away"}</p>
<p>Screen visible: {isVisible ? "Yes" : "Hidden"}</p>
<p>App focused: {isFocused ? "Yes" : "Unfocused"}</p>
</div>
);
}| Option | Type | Default | Description |
|---|---|---|---|
onActivate |
() => void |
undefined |
Callback fired exactly once for each inactive -> active transition. |
onDeactivate |
() => void |
undefined |
Callback fired exactly once for each active -> inactive transition. |
requireWindowFocus |
boolean |
true |
When true, the window must have user focus in addition to being visible. When false, document visibility alone determines activity. |
initialActive |
boolean |
true |
Deterministic SSR baseline state before client-side hydration. |
| Property | Type | Description |
|---|---|---|
isActive |
boolean |
true when the tab is currently active. |
wasActive |
boolean | undefined |
Prior confirmed active state. Stays undefined until the first real transition occurs to prevent hydration false-positives. |
isVisible |
boolean |
Direct document visibility (document.visibilityState === "visible"). |
isFocused |
boolean |
Direct document/window focus state. |
lastActiveAt |
number | null |
Timestamp (ms) of the most recent confirmation of active state, or null. |
lastInactiveAt |
number | null |
Timestamp (ms) of the most recent confirmation of inactive state, or null. |
Built with ❤️ by Saurav-TB-Pandey. | Report an Issue | NPM