Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

Connection status

When the client can't reach Stream, the SDK says why: "Waiting for network…" when the device is offline, "Reconnecting…" when the network is fine but the connection to Stream dropped. When the connection comes back, the client brings what's on screen up to date by itself. This page covers what users get, how the parts fit together, and how to show the status in your app.

What users get

The user's laptop loses its Wi-Fi on a train:

  1. The browser reports that the device is offline. Chat publishes "Waiting for network…" at once.
  2. The app renders a banner for it, so the user sees "Waiting for network…" at the top of the chat. A screen reader reads "Loading notification: Waiting for network…".
  3. The Wi-Fi comes back, and so does the connection. The banner goes away.
  4. The client reloads the channel lists and the open channel. The messages the user missed appear.

Later, the connection to Stream drops while the Wi-Fi works, because the server closed it or a health check timed out:

  • If it comes back within five seconds, the user sees nothing. Most drops recover in under a second.
  • If it doesn't, the banner shows "Reconnecting…" until it does.

The banner is the app's part. Chat publishes the message but renders nothing for it, so without a banner the user sees no status, and only a screen reader hears it.

How it works across the layers

State: two connection facts and recovery

The client keeps two separate facts, because they disagree in both directions:

  • client.networkConnection.state holds isOnline: whether the device has a network. It's undefined while unknown, so test it with === false.
  • client.wsConnection.state holds isHealthy: whether the WebSocket to Stream is up. It records a drop the moment it happens.

After a reconnect, client.connectionRecovery reloads the channel lists that have loaded, the active channels and the active threads, then dispatches connection.recovered. Connection explains both stores, the network reporter outside the browser, and recovery.

UI: the lost-connection notification

Chat watches both stores and publishes one system notification while the client can't reach Stream:

  • "Waiting for network…" as soon as the device reports it's offline;
  • "Reconnecting…" when the socket has been down for client.wsConnection.config.offlineNotificationDisplayDelayMs, five seconds by default, while the network is up or unknown.

Both have the type system:network:connection:lost and the severity loading. They stay until the connection is back, then Chat removes them. The hook that does this, useReportLostConnectionSystemNotification, is exported too.

A system notification carries the system tag, so no NotificationList shows it. The app renders it with useSystemNotifications(), anywhere inside Chat. NotificationAnnouncer reads it to screen readers like any other notification. System notifications covers them.

After a reconnect, the components re-render from the stores recovery reloads. Which channels and threads reload depends on what's active:

  • Channel marks the channel it shows active, so an open channel always reloads.
  • Thread doesn't. An open thread reloads only while something holds it active, such as useActiveThread({ activeThread }) from stream-chat-react/slot-layout, which holds it while the window has focus. Otherwise the thread is marked stale, and loads again the next time it's opened.

For a status UI of your own, useNetworkConnectionState(), useNetworkConnectionStateSelector(selector) and useWSConnectionState() read the two stores.

Layout

The layout isn't involved: the status and the banner are the same wherever channels and threads open.

Turning it on or off

The connection status is always on, for every user, and there's no capability or dashboard setting for it. Chat always publishes the notification. Whether users see it depends on whether the app renders a banner.

What you can change is in client.config:

  • client.wsConnection.offlineNotificationDisplayDelayMs: how long a socket drop lasts before "Reconnecting…" shows. 0 shows it on the next tick.
  • client.connectionRecovery.enabled: whether the client reloads lists, channels and threads after a reconnect. With it off, connection.recovered doesn't fire either.
  • client.networkConnection.statusReporter: what tells the client about the device's network, outside the browser.
client.config.set({
  client: { wsConnection: { offlineNotificationDisplayDelayMs: 2000 } },
});

The delay is read at each drop, so a change applies from the next one. Configuration explains client.config, and Connection the other settings.

Customizing

Showing the banner

Render the lost-connection notification inside Chat, above the rest of the layout:

import type { PropsWithChildren } from "react";
import type { Notification } from "stream-chat";
import { Chat, useSystemNotifications } from "stream-chat-react";

// Declared outside the component, so the subscription stays the same across renders.
const isConnectionLost = (notification: Notification) =>
  notification.type === "system:network:connection:lost";

const ConnectionBanner = () => {
  const [lostConnection] = useSystemNotifications({ filter: isConnectionLost });
  if (!lostConnection) return null;

  return <div className="connection-banner">{lostConnection.message}</div>;
};

export const App = ({ children }: PropsWithChildren) => (
  <Chat client={client}>
    <ConnectionBanner />
    {children}
  </Chat>
);

The banner has no live region of its own, because NotificationAnnouncer already reads the notification to screen readers. The message is already translated, and says which connection is down. To change the wording, override the translation keys chat.reportLostConnection.waitingNetwork.text and chat.reportLostConnection.reconnecting.text, as Localization explains.

The System notification banner recipe builds a complete banner, with an icon per severity, an animation, and system notifications of your own.

A status of your own

To decide the copy yourself, read the two stores. This component waits before it says "Reconnecting…", as Chat does, so a short drop shows nothing:

import { useEffect, useState } from "react";
import type { NetworkConnectionState } from "stream-chat";
import {
  useChatContext,
  useNetworkConnectionStateSelector,
  useWSConnectionState,
} from "stream-chat-react";

const selectIsOnline = ({ isOnline }: NetworkConnectionState) => ({
  isOnline,
});

export const ConnectionStatus = () => {
  const { client } = useChatContext();
  const { isOnline } = useNetworkConnectionStateSelector(selectIsOnline) ?? {};
  const { isHealthy = true } = useWSConnectionState() ?? {};
  const [isReconnecting, setIsReconnecting] = useState(false);

  useEffect(() => {
    if (isHealthy) {
      setIsReconnecting(false);
      return;
    }
    const delayMs =
      client.wsConnection.config.offlineNotificationDisplayDelayMs;
    const timer = setTimeout(() => setIsReconnecting(true), delayMs);
    return () => clearTimeout(timer);
  }, [client, isHealthy]);

  // `=== false`: `undefined` means the network status is unknown, not offline.
  if (isOnline === false) return <p>You're offline</p>;
  if (isReconnecting) return <p>Reconnecting…</p>;
  return null;
};

There's no hook that combines the two facts. Whether "no network and no socket" reads as offline or as reconnecting is a choice for your copy. Declare the selector at module scope: an inline one resubscribes on every render. Reading state in components explains why.

Refreshing your own data after a reconnect

The client reloads its own lists, channels and threads. Data the app loads itself, such as results of a one-off query, is reloaded on connection.recovered:

import { useEffect, useState } from "react";
import { useChatContext } from "stream-chat-react";

export const TotalUnreadBadge = () => {
  const { client } = useChatContext();
  const [totalUnread, setTotalUnread] = useState(0);

  useEffect(() => {
    const load = async () => {
      const { total_unread_count } = await client.unreadCounts();
      setTotalUnread(total_unread_count);
    };

    void load();
    const { unsubscribe } = client.on("connection.recovered", () => {
      void load();
    });
    return unsubscribe;
  }, [client]);

  return <span className="total-unread">{totalUnread}</span>;
};

connection.recovered explains when the event fires.

What screen readers hear

NotificationAnnouncer reads the lost-connection notification as "Loading notification: Reconnecting…". To leave it out, or reword it, replace the announcer as Customizing notification announcements shows.

Reference

  • Connection: client.wsConnection, client.networkConnection, client.connectionRecovery and connection.recovered.
  • System notifications: the lost-connection notification and useSystemNotifications.
  • Connection hooks: useNetworkConnectionState, useNetworkConnectionStateSelector, useWSConnectionState and useReportLostConnectionSystemNotification.
  • Notification hooks: useSystemNotifications and useNotificationApi.
  • System notification banner: a complete banner.
  • Configuration tree: the wsConnection, networkConnection and connectionRecovery settings.