April 2, 2026

By ReactUse Team

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

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<string | null>(null);
  const [status, setStatus] = useState<
    "CONNECTING" | "CONNECTED" | "DISCONNECTED"
  >("DISCONNECTED");
  const [error, setError] = useState<Event | null>(null);
  const esRef = useRef<EventSource | null>(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 };
}

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 (
    <div>
      <div>
        Status: {status}
        {status === "DISCONNECTED" && (
          <button onClick={open}>Reconnect</button>
        )}
        {status === "CONNECTED" && (
          <button onClick={close}>Disconnect</button>
        )}
      </div>

      {error && <div className="error">Connection error occurred</div>}

      <div className="log-entry">
        <span className="event-type">{event}</span>
        <span className="event-id">#{lastEventId}</span>
        <pre>{data}</pre>
      </div>
    </div>
  );
}

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<Notification[]>([]);
  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 (
    <div>
      <h2>
        Live Notifications
        <span className={`status-dot status-${status.toLowerCase()}`} />
      </h2>
      {notifications.map((n) => (
        <div key={n.id} className={`notification notification-${n.severity}`}>
          <strong>{n.title}</strong>
          <p>{n.body}</p>
        </div>
      ))}
    </div>
  );
}

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 <token>, 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<string | null>(null);
  const [status, setStatus] = useState<string>("DISCONNECTED");
  const abortRef = useRef<AbortController | null>(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 };
}

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 (
    <div>
      <div>Connection: {status}</div>
      {error && <div className="error">{error.message}</div>}
      <pre>{data}</pre>
    </div>
  );
}

Key differences from useEventSource:

FeatureuseEventSourceuseFetchEventSource
Custom headersNoYes
POST requestsNoYes
Request bodyNoYes
Based onNative EventSourcefetch API
Auto-reconnectYesYes
Named eventsYes (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 (
    <div className="chat">
      {messages.map((msg, i) => (
        <div key={i} className={`message message-${msg.role}`}>
          {msg.content}
        </div>
      ))}
      {streamedResponse && (
        <div className="message message-assistant">{streamedResponse}</div>
      )}
      <div className="input-row">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && sendMessage()}
          placeholder="Type a message..."
        />
        <button onClick={sendMessage} disabled={status === "CONNECTING"}>
          Send
        </button>
      </div>
    </div>
  );
}

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<string | undefined>();

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

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 (
    <div className="network-panel">
      <div>
        Status: {online ? "Online" : "Offline"}
        {previous !== undefined && previous !== online && (
          <span>
            {" "}
            (was {previous ? "online" : "offline"}, changed{" "}
            {since?.toLocaleTimeString()})
          </span>
        )}
      </div>
      <div>Connection: {type ?? "unknown"}</div>
      <div>Effective type: {effectiveType ?? "unknown"}</div>
      <div>Downlink: {downlink ? `${downlink} Mbps` : "unknown"}</div>
      <div>RTT: {rtt ? `${rtt}ms` : "unknown"}</div>
      <div>Data saver: {saveData ? "enabled" : "disabled"}</div>
    </div>
  );
}

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 (
    <div className="offline-banner">
      You are offline. Real-time updates are paused.
    </div>
  );
}

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 (
    <div>
      {!online && (
        <div className="banner">Offline -- showing cached scores</div>
      )}
      {effectiveType === "slow-2g" && (
        <div className="banner">Slow connection -- updates reduced</div>
      )}
      {/* Scoreboard content using pollingInterval */}
    </div>
  );
}

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<T>(channelName: string) {
  const [data, setData] = useState<T | undefined>();
  const channelRef = useRef<BroadcastChannel | null>(null);

  useEffect(() => {
    if (typeof BroadcastChannel === "undefined") return;

    const channel = new BroadcastChannel(channelName);
    channelRef.current = channel;

    const handleMessage = (event: MessageEvent<T>) => {
      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 };
}

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 <div>Cross-tab sync not available in this browser.</div>;
  }

  return (
    <div>
      <button onClick={() => broadcast("NEW_DATA", { count: 42 })}>
        Share data with other tabs
      </button>
      {error && <div className="error">Sync error</div>}
      {isClosed && <div className="warning">Channel closed</div>}
    </div>
  );
}

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 <D, P> 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<TabMessage, TabMessage>({
    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<MetricEvent[]>([]);
  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 (
    <div className="dashboard">
      {/* Connection Status Bar */}
      <header className="status-bar">
        <div className="status-indicators">
          <span className={`dot ${isOnline ? "green" : "red"}`} />
          <span>
            {isOnline ? "Online" : "Offline"}
            {effectiveType && ` (${effectiveType})`}
            {rtt && ` -- ${rtt}ms RTT`}
          </span>
        </div>
        <div className="tab-info">
          {isLeader ? "Leader tab (SSE active)" : "Follower tab (via broadcast)"}
          <span className={`dot ${sseStatus === "CONNECTED" ? "green" : "yellow"}`} />
        </div>
      </header>

      {/* Offline Banner */}
      {!isOnline && (
        <div className="offline-banner">
          You are offline. Showing the last {metrics.length} cached metrics.
          Data will resume when your connection is restored.
        </div>
      )}

      {/* Metrics Grid */}
      {latestMetric && (
        <div className="metrics-grid">
          <MetricCard
            label="CPU Usage"
            value={`${latestMetric.cpu.toFixed(1)}%`}
            status={latestMetric.cpu > 80 ? "danger" : "normal"}
          />
          <MetricCard
            label="Memory"
            value={`${latestMetric.memory.toFixed(1)}%`}
            status={latestMetric.memory > 90 ? "danger" : "normal"}
          />
          <MetricCard
            label="Requests/sec"
            value={latestMetric.requests.toLocaleString()}
            status="normal"
          />
          <MetricCard
            label="Errors/sec"
            value={latestMetric.errors.toLocaleString()}
            status={latestMetric.errors > 10 ? "danger" : "normal"}
          />
        </div>
      )}

      {/* Sparkline Chart (last 100 data points) */}
      <div className="chart-section">
        <h3>CPU Over Time</h3>
        <div className="sparkline">
          {metrics.map((m, i) => (
            <div
              key={i}
              className="bar"
              style={{
                height: `${m.cpu}%`,
                backgroundColor: m.cpu > 80 ? "#ef4444" : "#22c55e",
              }}
            />
          ))}
        </div>
      </div>
    </div>
  );
}

function MetricCard({
  label,
  value,
  status,
}: {
  label: string;
  value: string;
  status: "normal" | "danger";
}) {
  return (
    <div className={`metric-card metric-${status}`}>
      <div className="metric-label">{label}</div>
      <div className="metric-value">{value}</div>
    </div>
  );
}

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:

  1. Uses a single SSE connection across all tabs (saving server resources)
  2. Automatically reconnects with adaptive backoff based on connection quality
  3. Shows real-time network status to the user
  4. Degrades gracefully when offline
  5. Shares data instantly across every open tab

When to Use Which Hook

ScenarioHookWhy
Public SSE endpointuseEventSourceSimple, native EventSource
SSE with auth headersuseFetchEventSourceCustom headers via fetch
SSE with POST bodyuseFetchEventSourceSupports request bodies
Simple online/offline checkuseOnlineReturns a single boolean
Detailed connection infouseNetworkDownlink, RTT, effective type
Cross-tab messaginguseBroadcastChannelIn-memory, no persistence
Cross-tab + persistenceuseBroadcastChannel + useLocalStorageBest of both

Installation

npm install @reactuses/core

Or with your preferred package manager:

pnpm add @reactuses/core
yarn add @reactuses/core
  • 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 →