Building Real-Time Features in React Without WebSocket Libraries
<h1> Building Real-Time Features in React Without WebSocket Libraries </h1> <p>When developers hear "real-time," they reach for WebSocket libraries. Socket.IO, Pusher, Ably -- the ecosystem is full of them. But many real-time features do not need bidirectional communication. A stock ticker, a notification feed, a deployment log, a live sports score -- all of these are one-directional streams from server to client. For these use cases, the browser already has a built-in protocol that is simpler, lighter, and automatically reconnects: <strong>Server-Sent Events (SSE)</strong>.</p> <p>Combine SSE with the Network Information API for connection awareness, and the BroadcastChannel API for cross-tab coordination, and you have a complete real-time toolkit -- zero WebSocket libraries required. In
Building Real-Time Features in React Without WebSocket Libraries
When developers hear "real-time," they reach for WebSocket libraries. Socket.IO, Pusher, Ably -- the ecosystem is full of them. But many real-time features do not need bidirectional communication. A stock ticker, a notification feed, a deployment log, a live sports score -- all of these are one-directional streams from server to client. For these use cases, the browser already has a built-in protocol that is simpler, lighter, and automatically reconnects: Server-Sent Events (SSE).
Combine SSE with the Network Information API for connection awareness, and the BroadcastChannel API for cross-tab coordination, and you have a complete real-time toolkit -- zero WebSocket libraries required. In this article, we will build each piece from scratch first, see where the manual approach breaks down, then replace it with hooks from ReactUse that handle all the edge cases in a few lines.
1. Server-Sent Events with useEventSource
What Are Server-Sent Events?
Server-Sent Events (SSE) is a standard that lets a server push updates to the browser over a plain HTTP connection. Unlike WebSockets, SSE is unidirectional -- the server sends, the client receives. The browser's native EventSource API handles connection management, automatic reconnection, and event parsing out of the box.
// A basic SSE endpoint (server side, for reference) // GET /api/notifications // Content-Type: text/event-stream // // data: {"message": "New deployment started"} // id: 1 // // data: {"message": "Deployment complete"} // id: 2// A basic SSE endpoint (server side, for reference) // GET /api/notifications // Content-Type: text/event-stream // // data: {"message": "New deployment started"} // id: 1 // // data: {"message": "Deployment complete"} // id: 2Enter fullscreen mode
Exit fullscreen mode
The Manual Way
Let us connect to an SSE endpoint in React without any libraries.
import { useState, useEffect, useRef } from "react";
function useManualEventSource(url: string) { const [data, setData] = useState(null); const [status, setStatus] = useState< "CONNECTING" | "CONNECTED" | "DISCONNECTED"
("DISCONNECTED"); const [error, setError] = useState(null); const esRef = useRef(null); const retriesRef = useRef(0);
useEffect(() => { const connect = () => { setStatus("CONNECTING"); const es = new EventSource(url); esRef.current = es;
es.onopen = () => { setStatus("CONNECTED"); setError(null); retriesRef.current = 0; };
es.onmessage = (event) => { setData(event.data); };
es.onerror = (err) => { setError(err); setStatus("DISCONNECTED"); es.close(); esRef.current = null;
// Manual reconnection logic retriesRef.current += 1; if (retriesRef.current < 5) { setTimeout(connect, 1000 * retriesRef.current); } }; };*
connect();
return () => { esRef.current?.close(); esRef.current = null; }; }, [url]);
return { data, status, error }; }`
Enter fullscreen mode
Exit fullscreen mode
That is about 45 lines, and it already has problems:
-
Named events are not handled. SSE supports custom event types (e.g., event: deploy-status), but onmessage only catches unnamed messages. Supporting named events requires calling addEventListener for each event type and cleaning up each listener on unmount.
-
Reconnection is naive. The code retries up to 5 times with linear backoff, but there is no way to configure the limit, the delay, or a failure callback.
-
No explicit close/reopen. If the user navigates away and comes back, or if you want to pause the stream while the tab is hidden, you need more state tracking.
-
SSR will crash. EventSource does not exist on the server.
With useEventSource
The useEventSource hook from ReactUse handles all of this.
import { useEventSource } from "@reactuses/core";
function DeploymentLog() { const { data, status, error, event, lastEventId, close, open } = useEventSource("/api/deployments/stream", ["deploy-start", "deploy-end"], { autoReconnect: { retries: 5, delay: 2000, onFailed: () => console.error("SSE connection permanently failed"), }, });
return (
Status: {status} {status === "DISCONNECTED" && ( Reconnect )} {status === "CONNECTED" && ( Disconnect )}
{error && Connection error occurred}
{event} #{lastEventId} {data}
); }`
Enter fullscreen mode
Exit fullscreen mode
Look at what you get for free:
-
Named event support. Pass an array of event names as the second argument, and the hook listens to each one. The event return value tells you which event type fired.
-
Configurable auto-reconnect. Set the number of retries, the delay between attempts, and a callback when all retries are exhausted.
-
Explicit close and reopen. Call close() to disconnect, open() to reconnect -- useful for pausing streams in background tabs.
-
SSR safe. The hook guards against EventSource being undefined on the server.
-
Last event ID tracking. The lastEventId value lets you resume from where you left off if the server supports it.
A Practical Example: Live Notification Feed
import { useEventSource } from "@reactuses/core"; import { useState, useEffect } from "react";import { useEventSource } from "@reactuses/core"; import { useState, useEffect } from "react";interface Notification { id: string; title: string; body: string; severity: "info" | "warning" | "error"; }
function NotificationFeed() { const [notifications, setNotifications] = useState([]); const { data, status, event } = useEventSource( "/api/notifications/stream", ["info", "warning", "error"], { autoReconnect: { retries: -1, // retry forever delay: 3000, }, } );
useEffect(() => { if (data) { try { const notification: Notification = { ...JSON.parse(data), severity: event as Notification["severity"], }; setNotifications((prev) => [notification, ...prev].slice(0, 50)); } catch { // malformed data, ignore } } }, [data, event]);
return (
Live Notifications
{notifications.map((n) => (
{n.title} {n.body}
))}
); }`
Enter fullscreen mode
Exit fullscreen mode
The hook handles the SSE lifecycle. Your component only deals with parsing data and rendering UI.
2. Authenticated SSE Streams with useFetchEventSource
The Problem with Native EventSource
The native EventSource API has a major limitation: you cannot set custom headers. That means no Authorization: Bearer , no custom X-Request-ID, and no POST requests with a body. If your SSE endpoint requires authentication, EventSource is not enough.
The common workaround is to pass the token as a query parameter (/api/stream?token=abc), but that leaks credentials into server logs, browser history, and referrer headers. It is a security anti-pattern.
The Manual Way
To send headers with an SSE-like connection, you need to use fetch with a readable stream -- and handle chunked parsing, reconnection, and abort signals yourself.
import { useState, useEffect, useRef } from "react";
function useManualFetchSSE(url: string, token: string) { const [data, setData] = useState(null); const [status, setStatus] = useState("DISCONNECTED"); const abortRef = useRef(null);
useEffect(() => { const controller = new AbortController(); abortRef.current = controller; setStatus("CONNECTING");
const connect = async () => {
try {
const response = await fetch(url, {
headers: {
Authorization: Bearer ${token},
Accept: "text/event-stream",
},
signal: controller.signal,
});
if (!response.ok) throw new Error(HTTP ${response.status});
if (!response.body) throw new Error("No response body");
setStatus("CONNECTED"); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = "";
while (true) { const { done, value } = await reader.read(); if (done) break;
buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n\n"); buffer = lines.pop() || "";
for (const chunk of lines) { const dataLine = chunk .split("\n") .find((l) => l.startsWith("data: ")); if (dataLine) { setData(dataLine.slice(6)); } } } } catch (err) { if (!controller.signal.aborted) { setStatus("DISCONNECTED"); // reconnection logic here... } } };
connect(); return () => controller.abort(); }, [url, token]);
return { data, status }; }`
Enter fullscreen mode
Exit fullscreen mode
This is already 55+ lines, and it is incomplete. It does not handle named events, event IDs, reconnection with backoff, or POST requests. Parsing the SSE text protocol by hand is error-prone.
With useFetchEventSource
The useFetchEventSource hook wraps the @microsoft/fetch-event-source library in a React-friendly API. It supports custom headers, POST requests with bodies, and all the reconnection logic you need.
import { useFetchEventSource } from "@reactuses/core";
function AuthenticatedStream() {
const { data, status, event, error, close, open } = useFetchEventSource(
"/api/private/stream",
{
method: "POST",
headers: {
Authorization: Bearer ${getAccessToken()},
"X-Request-ID": crypto.randomUUID(),
},
body: JSON.stringify({
channels: ["deployments", "alerts"],
}),
autoReconnect: {
retries: 10,
delay: 2000,
onFailed: () => {
// Token might be expired -- redirect to login
window.location.href = "/login";
},
},
onOpen: () => console.log("Stream connected"),
onError: (err) => {
console.error("Stream error:", err);
return 5000; // retry after 5 seconds
},
}
);
return (
Connection: {status} {error && {error.message}} {data}
); }`
Enter fullscreen mode
Exit fullscreen mode
Key differences from useEventSource:
Feature useEventSource useFetchEventSource
Custom headers No Yes
POST requests No Yes
Request body No Yes
Based on
Native EventSource
fetch API
Auto-reconnect Yes Yes
Named events
Yes (via array)
Yes (via event field)
Use useEventSource when your endpoint is public or uses cookie-based auth. Use useFetchEventSource when you need token-based auth, custom headers, or POST requests.
A Practical Example: AI Chat Streaming
SSE is the standard protocol for streaming AI responses (OpenAI, Anthropic, and others all use it). Here is how to build a streaming chat UI with authentication.
import { useFetchEventSource } from "@reactuses/core"; import { useState, useEffect, useCallback } from "react";import { useFetchEventSource } from "@reactuses/core"; import { useState, useEffect, useCallback } from "react";function AIChatStream() { const [messages, setMessages] = useState< Array<{ role: string; content: string }>
([]); const [input, setInput] = useState(""); const [streamedResponse, setStreamedResponse] = useState("");
const { data, status, open, close } = useFetchEventSource(
"/api/chat/completions",
{
method: "POST",
headers: {
Authorization: Bearer ${getApiKey()},
},
body: JSON.stringify({
messages,
stream: true,
}),
immediate: false, // don't connect on mount
onOpen: () => setStreamedResponse(""),
}
);
// Accumulate streamed tokens useEffect(() => { if (data) { try { const parsed = JSON.parse(data); const token = parsed.choices?.[0]?.delta?.content; if (token) { setStreamedResponse((prev) => prev + token); } } catch { // ignore [DONE] or malformed chunks } } }, [data]);
const sendMessage = useCallback(() => { if (!input.trim()) return; setMessages((prev) => [...prev, { role: "user", content: input }]); setInput(""); open(); // start the SSE stream }, [input, open]);
return (
{messages.map((msg, i) => (
{msg.content}
))} {streamedResponse && ( {streamedResponse} )}
setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && sendMessage()} placeholder="Type a message..." />
Send
); }`
Enter fullscreen mode
Exit fullscreen mode
The immediate: false option is critical here -- we do not want the connection to open on mount. We call open() explicitly when the user sends a message.
3. Network Status Detection with useNetwork and useOnline
Real-time features are useless if the user is offline. Worse, they fail silently -- the SSE connection drops, fetch requests hang, and the UI shows stale data without any indication that something is wrong. Good real-time UIs are network-aware.
The Manual Way
import { useState, useEffect } from "react";
function useManualNetworkStatus() { const [isOnline, setIsOnline] = useState( typeof navigator !== "undefined" ? navigator.onLine : true ); const [connectionType, setConnectionType] = useState();
useEffect(() => { const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false);
window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline);
// Network Information API (not available in all browsers) const conn = (navigator as any).connection; if (conn) { const handleChange = () => { setConnectionType(conn.effectiveType); }; conn.addEventListener("change", handleChange); handleChange();
return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); conn.removeEventListener("change", handleChange); }; }
return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; }, []);
return { isOnline, connectionType }; }`
Enter fullscreen mode
Exit fullscreen mode
That is around 35 lines for just two pieces of information, and it does not track downlink speed, round-trip time, data saver mode, or the timestamp of the last status change. The Network Information API also uses vendor-prefixed properties (mozConnection, webkitConnection) that this code does not handle.
With useNetwork
The useNetwork hook returns the full picture.
import { useNetwork } from "@reactuses/core";
function NetworkDebugPanel() { const { online, previous, since, downlink, effectiveType, rtt, saveData, type, } = useNetwork();
return (
Status: {online ? "Online" : "Offline"} {previous !== undefined && previous !== online && (
{" "} (was {previous ? "online" : "offline"}, changed{" "} {since?.toLocaleTimeString()})
)}
Connection: {type ?? "unknown"}
Effective type: {effectiveType ?? "unknown"}
Downlink: {downlink ? ${downlink} Mbps : "unknown"}
RTT: {rtt ? ${rtt}ms : "unknown"}
Data saver: {saveData ? "enabled" : "disabled"}
); }`
Enter fullscreen mode
Exit fullscreen mode
The hook handles all the vendor prefixes, event listeners, and SSR safety. The previous and since fields are especially useful -- they let you show "You went offline 30 seconds ago" instead of just "Offline."
With useOnline
If you only need the boolean, useOnline is even simpler. It is a thin wrapper around useNetwork that returns just the online value.
import { useOnline } from "@reactuses/core";
function OfflineBanner() { const isOnline = useOnline();
if (isOnline) return null;
return (
You are offline. Real-time updates are paused.
); }`
Enter fullscreen mode
Exit fullscreen mode
A Practical Example: Adaptive Quality Streaming
The network information from useNetwork lets you adapt your application's behavior to the user's connection quality.
import { useNetwork } from "@reactuses/core"; import { useMemo } from "react";import { useNetwork } from "@reactuses/core"; import { useMemo } from "react";function useAdaptivePolling(baseInterval: number) { const { online, effectiveType, saveData } = useNetwork();
const interval = useMemo(() => { if (!online) return null; // stop polling when offline if (saveData) return baseInterval * 4; // respect data saver switch (effectiveType) { case "slow-2g": case "2g": return baseInterval * 3; case "3g": return baseInterval * 2; case "4g": default: return baseInterval; } }, [online, effectiveType, saveData, baseInterval]);*
return interval; }
function LiveScoreboard() { const pollingInterval = useAdaptivePolling(5000); const { online, effectiveType } = useNetwork();
return (
{!online && ( Offline -- showing cached scores )} {effectiveType === "slow-2g" && ( Slow connection -- updates reduced )} {/* Scoreboard content using pollingInterval */}
); }`
Enter fullscreen mode
Exit fullscreen mode
On a fast 4G connection, the scoreboard updates every 5 seconds. On a slow 2G connection, it updates every 15 seconds. Offline, it stops entirely and shows cached data. The user gets the best experience their connection can support.
4. Cross-Tab Communication with useBroadcastChannel
Real-time data often needs to be shared across browser tabs. If a user has your dashboard open in three tabs and a new notification arrives via SSE, all three tabs should show it -- but only one tab should maintain the SSE connection. The BroadcastChannel API makes this possible.
The Manual Way
import { useState, useEffect, useRef, useCallback } from "react";
function useManualBroadcastChannel(channelName: string) { const [data, setData] = useState(); const channelRef = useRef(null);
useEffect(() => { if (typeof BroadcastChannel === "undefined") return;
const channel = new BroadcastChannel(channelName); channelRef.current = channel;
const handleMessage = (event: MessageEvent) => { setData(event.data); };
const handleError = (event: MessageEvent) => { console.error("BroadcastChannel error:", event); };
channel.addEventListener("message", handleMessage); channel.addEventListener("messageerror", handleError);
return () => { channel.removeEventListener("message", handleMessage); channel.removeEventListener("messageerror", handleError); channel.close(); }; }, [channelName]);
const post = useCallback((message: T) => { channelRef.current?.postMessage(message); }, []);
return { data, post }; }`
Enter fullscreen mode
Exit fullscreen mode
This works for simple cases, but it does not track whether BroadcastChannel is supported, whether the channel is closed, error state, or timestamps for deduplication.
With useBroadcastChannel
The useBroadcastChannel hook provides a complete, type-safe wrapper.
import { useBroadcastChannel } from "@reactuses/core";
interface DashboardMessage { type: "NEW_DATA" | "USER_ACTION" | "TAB_CLOSING"; payload?: unknown; sourceTab: string; }
function DashboardSync() { const { data, post, isSupported, isClosed, error } = useBroadcastChannel< DashboardMessage, DashboardMessage
({ name: "dashboard-sync" });
const broadcast = (type: DashboardMessage["type"], payload?: unknown) => { post({ type, payload, sourceTab: sessionStorage.getItem("tab-id") || "unknown", }); };
useEffect(() => { if (data?.type === "NEW_DATA") { // Update local state with data from another tab console.log("Received data from tab:", data.sourceTab, data.payload); } }, [data]);
if (!isSupported) { return Cross-tab sync not available in this browser.; }
return (
broadcast("NEW_DATA", { count: 42 })}> Share data with other tabs
{error && Sync error} {isClosed && Channel closed}
); }`
Enter fullscreen mode
Exit fullscreen mode
The hook gives you:
-
isSupported -- check if BroadcastChannel is available before rendering sync-dependent UI.
-
isClosed -- know when the channel has been closed (by you or by the browser).
-
error -- handle message serialization errors.
-
timeStamp -- deduplicate messages when the same data is received multiple times.
-
Type safety -- generic parameters for received data type and posted data type.
5. Putting It All Together: A Real-Time Dashboard
Let us combine all five hooks into a production-style real-time dashboard. This dashboard:
-
Receives live metrics via SSE (with authentication)
-
Detects network status and adapts accordingly
-
Shares data across tabs so only one tab maintains the SSE connection
-
Shows connection health to the user
import { useFetchEventSource, useNetwork, useOnline, useBroadcastChannel, useEventSource, } from "@reactuses/core"; import { useState, useEffect, useCallback, useRef } from "react";import { useFetchEventSource, useNetwork, useOnline, useBroadcastChannel, useEventSource, } from "@reactuses/core"; import { useState, useEffect, useCallback, useRef } from "react";// --- Types ---
interface MetricEvent { timestamp: number; cpu: number; memory: number; requests: number; errors: number; }
interface TabMessage { type: "METRIC_UPDATE" | "CLAIM_LEADER" | "RELEASE_LEADER" | "HEARTBEAT"; payload?: MetricEvent; tabId: string; }
// --- Leader Election Hook ---
function useTabLeader(channelName: string) { const tabId = useRef(crypto.randomUUID()).current; const [isLeader, setIsLeader] = useState(false); const { data, post } = useBroadcastChannel({ name: channelName, });
useEffect(() => { // On mount, claim leadership after a short delay const timer = setTimeout(() => { post({ type: "CLAIM_LEADER", tabId }); setIsLeader(true); }, Math.random() * 200);*
return () => { clearTimeout(timer); post({ type: "RELEASE_LEADER", tabId }); }; }, [post, tabId]);
useEffect(() => { if (data?.type === "CLAIM_LEADER" && data.tabId !== tabId) { if (data.tabId > tabId) { setIsLeader(false); } } if (data?.type === "RELEASE_LEADER") { // Another tab released -- try to claim setTimeout(() => { post({ type: "CLAIM_LEADER", tabId }); setIsLeader(true); }, Math.random() * 100); } }, [data, tabId, post]);*
return { isLeader, tabId }; }
// --- Network-Aware SSE Hook ---
function useMetricsStream(enabled: boolean) { const { online, effectiveType } = useNetwork();
const { data, status, error, close, open } = useFetchEventSource(
"/api/metrics/stream",
{
headers: {
Authorization: Bearer ${getAccessToken()},
},
immediate: false,
autoReconnect: {
retries: -1,
delay: effectiveType === "4g" ? 2000 : 5000,
onFailed: () => console.error("Metrics stream failed permanently"),
},
}
);
// Connect/disconnect based on enabled flag and online status useEffect(() => { if (enabled && online) { open(); } else { close(); } }, [enabled, online, open, close]);
return { data, status, error }; }
// --- Main Dashboard Component ---
function RealtimeDashboard() { const [metrics, setMetrics] = useState([]); const isOnline = useOnline(); const { online, effectiveType, rtt } = useNetwork();
// Leader election -- only the leader tab opens the SSE connection const { isLeader, tabId } = useTabLeader("metrics-leader");
// SSE stream -- only active if this tab is the leader const { data: sseData, status: sseStatus } = useMetricsStream(isLeader);
// Cross-tab data sharing const { data: tabData, post: broadcastToTabs } = useBroadcastChannel< TabMessage, TabMessage
({ name: "metrics-data" });
// When the leader receives SSE data, broadcast it to other tabs useEffect(() => { if (isLeader && sseData) { try { const metric: MetricEvent = JSON.parse(sseData); setMetrics((prev) => [...prev, metric].slice(-100)); broadcastToTabs({ type: "METRIC_UPDATE", payload: metric, tabId, }); } catch { // malformed data } } }, [isLeader, sseData, broadcastToTabs, tabId]);
// When a non-leader tab receives broadcast data, update local state useEffect(() => { if (!isLeader && tabData?.type === "METRIC_UPDATE" && tabData.payload) { setMetrics((prev) => [...prev, tabData.payload!].slice(-100)); } }, [isLeader, tabData]);
const latestMetric = metrics[metrics.length - 1];
return (
{/* Connection Status Bar */}
{isOnline ? "Online" : "Offline"}
{effectiveType && (${effectiveType})}
{rtt && -- ${rtt}ms RTT}
{isLeader ? "Leader tab (SSE active)" : "Follower tab (via broadcast)"}
{/* Offline Banner */} {!isOnline && (
You are offline. Showing the last {metrics.length} cached metrics. Data will resume when your connection is restored.
)}
{/* Metrics Grid */} {latestMetric && (
80 ? "danger" : "normal"} /> 90 ? "danger" : "normal"} />
10 ? "danger" : "normal"} />
)}
{/* Sparkline Chart (last 100 data points) */}
CPU Over Time
{metrics.map((m, i) => ( 80 ? "#ef4444" : "#22c55e", }} /> ))}
); }
function MetricCard({ label, value, status, }: { label: string; value: string; status: "normal" | "danger"; }) { return (
{label} {value}
); }`
Enter fullscreen mode
Exit fullscreen mode
Here is what each hook contributes to this dashboard:
-
useFetchEventSource -- connects to the authenticated metrics SSE endpoint with automatic reconnection.
-
useEventSource -- could be used instead if the endpoint does not require auth headers (swap it in with zero API changes to the component).
-
useNetwork -- provides connection quality data (effectiveType, rtt) for the status bar and adaptive reconnection delays.
-
useOnline -- drives the offline banner and pauses the SSE connection when the network drops.
-
useBroadcastChannel -- enables leader election and cross-tab data sharing, so only one tab maintains the SSE connection while all tabs show live data.
The result is a dashboard that:
-
Uses a single SSE connection across all tabs (saving server resources)
-
Automatically reconnects with adaptive backoff based on connection quality
-
Shows real-time network status to the user
-
Degrades gracefully when offline
-
Shares data instantly across every open tab
When to Use Which Hook
Scenario Hook Why
Public SSE endpoint
useEventSource
Simple, native EventSource
SSE with auth headers
useFetchEventSource
Custom headers via fetch
SSE with POST body
useFetchEventSource
Supports request bodies
Simple online/offline check
useOnline
Returns a single boolean
Detailed connection info
useNetwork
Downlink, RTT, effective type
Cross-tab messaging
useBroadcastChannel
In-memory, no persistence
Cross-tab + persistence
useBroadcastChannel + useLocalStorage
Best of both
Installation
npm install @reactuses/core
Enter fullscreen mode
Exit fullscreen mode
Or with your preferred package manager:
pnpm add @reactuses/core yarn add @reactuses/corepnpm add @reactuses/core yarn add @reactuses/coreEnter fullscreen mode
Exit fullscreen mode
Related Hooks
-
useEventSource -- reactive Server-Sent Events with named event support and auto-reconnect
-
useFetchEventSource -- SSE via fetch, supporting custom headers, POST requests, and authentication
-
useNetwork -- detailed network status including connection type, downlink speed, and RTT
-
useOnline -- simple boolean for online/offline detection
-
useBroadcastChannel -- type-safe cross-tab messaging via the BroadcastChannel API
-
useDocumentVisibility -- track whether the current tab is visible
-
useLocalStorage -- persistent state with automatic cross-tab synchronization
ReactUse provides 100+ hooks for React. Explore them all →
DEV Community
https://dev.to/childrentime/building-real-time-features-in-react-without-websocket-libraries-4lm8Sign in to highlight and annotate this article

Conversation starters
Daily AI Digest
Get the top 5 AI stories delivered to your inbox every morning.
More about
releaseavailableupdate
6 metrics IT leaders can’t afford to ignore for business resilience
If you’re in IT, you know: what we don’t measure puts business resilience at risk. In the face of rising threat volumes, scaling complexity, and board-level scrutiny, tracking the right operational metrics isn’t just about visibility—it’s the foundation for proactive risk management and business continuity. Compliance and insurance demands are also driving the scrutiny around measuring cybersecurity programs. Recent findings from the 2026 N-able State of the SOC Report are clear: the threat landscape keeps shifting, automation and integration are now must-haves, and organizations delivering true resilience measure what matters most. Below are the six metrics that we use to move the needle from firefighting to futureproofing. 1. Mean time to detect (MTTD): The speed of awareness Attackers a

5 essential steps to bulletproof your endpoint security (and avoid the biggest mistakes)
Business resilience starts at the endpoint. Between March and December 2025, the N-able SOC processed over 900,000 alerts—and a staggering 18% originated from network and perimeter exploits that most endpoint-only security never saw. Attackers are constantly shifting tactics, and endpoints remain an exposed attack surface. The good news: the right proactive strategies put you in control, stopping threats before they ripple across your business. Here’s our concise, field-tested playbook to operationalize resilient endpoint security and avoid the single-layer fallacy that leaves half your risks unseen. 1. Start with full endpoint visibility—No blind spots allowed You can’t protect what you don’t know about. As mentioned in our State of the SOC report , network and perimeter threats flew unde

7 ways to improve your business resilience with backup and recovery
When your network goes down, your business stops. That’s a stark truth we see confirmed daily in incident response—and N-able’s 2026 State of the SOC Report only underscores it. Backup isn’t just an IT routine anymore; it’s the backbone of your business resilience strategy. Yet, too many teams leave gaps that threat actors are ready to exploit. Let’s get proactive. Here are seven common backup priorities and what we recommend to ensure your organization can recover from anything the modern threat landscape throws at you. 1. Prioritize your most critical data You can’t protect everything at the same level, and you shouldn’t try. Businesses focusing on mission-critical systems for backup and rapid recovery have significantly shorter downtime post-incident. The Fix: Identify revenue-driving a
Knowledge Map
Connected Articles — Knowledge Graph
This article is connected to other articles through shared AI topics and tags.
More in Products

5 critical steps to achieve business resilience in cybersecurity
What does it really take to keep your organization running when attackers strike? The answer is business resilience—being able to detect, contain, and recover fast enough that disruptions are minimized, customers stay confident, and operations keep moving. From the latest 2026 State of the SOC Report , which is based on more than 900,000 alerts observed between March and December 2025 from the Adlumin Managed Detection and Response (MDR) provided by the N-able SOC, we’ve seen firsthand where security strategies succeed—and where they fall short. Below, we break down five actionable ways to build true resilience for your IT environment, using real-world data, strategic guidance, and frameworks that leading IT teams put into practice today. 1. Stop trusting single-layer security If you’re de

6 metrics IT leaders can’t afford to ignore for business resilience
If you’re in IT, you know: what we don’t measure puts business resilience at risk. In the face of rising threat volumes, scaling complexity, and board-level scrutiny, tracking the right operational metrics isn’t just about visibility—it’s the foundation for proactive risk management and business continuity. Compliance and insurance demands are also driving the scrutiny around measuring cybersecurity programs. Recent findings from the 2026 N-able State of the SOC Report are clear: the threat landscape keeps shifting, automation and integration are now must-haves, and organizations delivering true resilience measure what matters most. Below are the six metrics that we use to move the needle from firefighting to futureproofing. 1. Mean time to detect (MTTD): The speed of awareness Attackers a

6 critical mistakes that undermine cyber resilience (and how to fix them)
Silos are the enemy of business resilience. As IT leaders, we’ve all felt the pain: the backup administrator, SOC analyst, and endpoint engineer operating in separate worlds—often meeting for the first time in the chaos of a live cyberattack. The result? Delayed responses, missed signals, and greater impact on the business. The N-able 2026 State of the SOC Report leaves no doubt. In just one year, 18% of all security alerts came from network and perimeter exploits—risks many endpoint-only teams never saw coming. Even scarier? 50% of attacks completely bypass endpoint controls. You can’t afford to be siloed. Here’s where most organizations go wrong—and the six crucial steps you need to take to align our teams, tools, and processes for true business resilience. Mistake 1: Unclear roles and r

5 essential steps to bulletproof your endpoint security (and avoid the biggest mistakes)
Business resilience starts at the endpoint. Between March and December 2025, the N-able SOC processed over 900,000 alerts—and a staggering 18% originated from network and perimeter exploits that most endpoint-only security never saw. Attackers are constantly shifting tactics, and endpoints remain an exposed attack surface. The good news: the right proactive strategies put you in control, stopping threats before they ripple across your business. Here’s our concise, field-tested playbook to operationalize resilient endpoint security and avoid the single-layer fallacy that leaves half your risks unseen. 1. Start with full endpoint visibility—No blind spots allowed You can’t protect what you don’t know about. As mentioned in our State of the SOC report , network and perimeter threats flew unde

Discussion
Sign in to join the discussion
No comments yet — be the first to share your thoughts!