Modern web apps are incredibly dynamic. We poll APIs, fetch real-time updates over WebSockets, run complex UI transitions, and render interactive Canvas graphics. But what happens when your user switches tabs or minimizes their browser?
If your application keeps running at full throttle, you are:
- Wasting your server's resources and driving up API costs.
- Draining your users' device batteries (especially on mobile).
- Degrading overall system performance.
To solve this, we've introduced the useTabVisibility hook in the latest update of react-hook-lab. This hook makes it painless to track when a tab is truly active and responsive.
Why not just use document.visibilityState?
While the native page visibility API is useful, it has several limitations:
- It doesn't track window focus, which is essential on multi-monitor setups where a tab might be visible but completely unfocused.
- It doesn't handle mobile browser freezing/resuming (BFCache) elegantly out of the box.
- Simple event listeners can cause "hydration mismatch" errors in Server-Side Rendering (SSR) environments like Next.js.
The useTabVisibility hook addresses all of these pain points with a zero-tearing, SSR-safe implementation.
Code Example 1: Pausing Background Polling
In this example, we automatically pause and resume polling depending on whether the user is actively viewing the tab.
import React, { useEffect } from "react";
import { useTabVisibility } from "react-hook-lab";
export function DataFeeder() {
const { isActive } = useTabVisibility({
onActivate: () => console.log("Tab activated! Resuming sync..."),
onDeactivate: () => console.log("Tab deactivated! Pausing sync..."),
});
useEffect(() => {
if (!isActive) return;
const interval = setInterval(() => {
console.log("Fetching live updates...");
}, 5000);
return () => clearInterval(interval);
}, [isActive]);
return (
<div style={{ padding: "20px", border: "1px solid #ccc" }}>
<h3>Data Sync Status</h3>
<p>Sync is currently: <strong>{isActive ? "ACTIVE" : "PAUSED"}</strong></p>
</div>
);
}
Code Example 2: Dynamic Page Title & Focus Diagnostics
Here, we disable the focus requirement so that the hook only tracks if the tab is visible. We also display transition timestamps.
import React, { useEffect } from "react";
import { useTabVisibility } from "react-hook-lab";
export function DiagnosticsDashboard() {
const {
isActive,
isVisible,
isFocused,
lastActiveAt,
lastInactiveAt
} = useTabVisibility({
requireWindowFocus: false, // Remains active even if the window loses focus
});
useEffect(() => {
document.title = isActive ? "Dashboard" : "We miss you!";
}, [isActive]);
const formatTime = (ts: number | null) => ts ? new Date(ts).toLocaleTimeString() : "Never";
return (
<div style={{ padding: "20px", fontFamily: "sans-serif" }}>
<h2>App Diagnostics</h2>
<ul>
<li>Is Visible: {isVisible ? "Yes" : "No"}</li>
<li>Is Focused: {isFocused ? "Yes" : "No"}</li>
<li>Active (Ignore Focus): {isActive ? "Yes" : "No"}</li>
<li>Last Activated: {formatTime(lastActiveAt)}</li>
<li>Last Deactivated: {formatTime(lastInactiveAt)}</li>
</ul>
</div>
);
}
Key API Options & Return Types
The hook is highly configurable:
-
requireWindowFocus: When true (default), both the page visibility and window focus are required for the tab to be considered active. -
onActivate/onDeactivate: Lifecycle callbacks triggered during status transitions. -
wasActive: Stores the previous state, preventing false positives during initial client-side hydration.
Resources
- GitHub Repository: Saurav-TB-Pandey/react-hook-lab
- NPM Package: react-hook-lab
- LinkedIn Profile: Saurav Pandey on LinkedIn
Originally published on my blog. You can read the alternative breakdown here.
Top comments (0)