Live
Black Hat USADark ReadingBlack Hat AsiaAI BusinessI Brute-Forced 2 Million Hashes to Get a Shiny Legendary Cat in My Terminal. It Has Max SNARK and a Propeller Hat.DEV CommunityHave to do enough for my talk, "Is AI Getting Reports Wrong? Try Google LookML, Your Data Dictionary!" at Google NEXT 2026DEV CommunityTaming the Ingredient Sourcing Nightmare with AI AutomationDEV Community# 🚀 How to Build a High-Performance Landing Page with Next.js 15 and Tailwind v4DEV CommunityClaude Code Architecture Explained: Agent Loop, Tool System, and Permission Model (Rust Rewrite Analysis)DEV CommunityThe Data Structure That's Okay With Being WrongDEV CommunityHow to Auto-Index Your URLs with Google Search Console APIDEV CommunityThe Indestructible FutureLessWrong AIBuilding Real-Time Features in React Without WebSocket LibrariesDEV CommunityChatGPT Maker OpenAI Valued at $852B After Record $122B Funding Round - Bitcoin.com NewsGoogle News: ChatGPTParameter Count Is the Worst Way to Pick a Model on 8GB VRAMDEV CommunityTreeline, which is building an AI and software-first alternative to legacy corporate IT systems, raised a $25M Series A led by Andreessen Horowitz (Lily Mae Lazarus/Fortune)TechmemeBlack Hat USADark ReadingBlack Hat AsiaAI BusinessI Brute-Forced 2 Million Hashes to Get a Shiny Legendary Cat in My Terminal. It Has Max SNARK and a Propeller Hat.DEV CommunityHave to do enough for my talk, "Is AI Getting Reports Wrong? Try Google LookML, Your Data Dictionary!" at Google NEXT 2026DEV CommunityTaming the Ingredient Sourcing Nightmare with AI AutomationDEV Community# 🚀 How to Build a High-Performance Landing Page with Next.js 15 and Tailwind v4DEV CommunityClaude Code Architecture Explained: Agent Loop, Tool System, and Permission Model (Rust Rewrite Analysis)DEV CommunityThe Data Structure That's Okay With Being WrongDEV CommunityHow to Auto-Index Your URLs with Google Search Console APIDEV CommunityThe Indestructible FutureLessWrong AIBuilding Real-Time Features in React Without WebSocket LibrariesDEV CommunityChatGPT Maker OpenAI Valued at $852B After Record $122B Funding Round - Bitcoin.com NewsGoogle News: ChatGPTParameter Count Is the Worst Way to Pick a Model on 8GB VRAMDEV CommunityTreeline, which is building an AI and software-first alternative to legacy corporate IT systems, raised a $25M Series A led by Andreessen Horowitz (Lily Mae Lazarus/Fortune)Techmeme

Building Real-Time Features in React Without WebSocket Libraries

DEV Communityby reactuse.comApril 2, 202639 min read0 views
Source Quiz

<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

Enter 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";

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";

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";

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";

// --- 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/core

Enter 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 →

Was this article helpful?

Sign in to highlight and annotate this article

AI
Ask AI about this article
Powered by AI News Hub · full article context loaded
Ready

Conversation starters

Ask anything about this article…

Daily AI Digest

Get the top 5 AI stories delivered to your inbox every morning.

More about

releaseavailableupdate

Knowledge Map

Knowledge Map
TopicsEntitiesSource
Building Re…releaseavailableupdateproductapplicationfeatureDEV Communi…

Connected Articles — Knowledge Graph

This article is connected to other articles through shared AI topics and tags.

Knowledge Graph100 articles · 250 connections
Scroll to zoom · drag to pan · click to open

Discussion

Sign in to join the discussion

No comments yet — be the first to share your thoughts!

More in Products