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:
- The browser reports that the device is offline.
Chatpublishes "Waiting for network…" at once. - 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…".
- The Wi-Fi comes back, and so does the connection. The banner goes away.
- 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.stateholdsisOnline: whether the device has a network. It'sundefinedwhile unknown, so test it with=== false.client.wsConnection.stateholdsisHealthy: 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, of type system:network:connection:lost, while the client can't reach Stream. System notifications explains when it says "Waiting for network…" or "Reconnecting…", and how long it stays.
After a reconnect, the components re-render from the stores recovery reloads. Which channels and threads reload depends on what's active:
Channelmarks the channel it shows active, so an open channel always reloads.Threaddoesn't. An open thread reloads only while something holds it active, such asuseActiveThread({ activeThread })fromstream-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.0shows it on the next tick.client.connectionRecovery.enabled: whether the client reloads channel paginators, channels and threads after a reconnect. With it off, only the thread list reloads, andconnection.recoveredstill fires after each reconnect. See Turning recovery off or running it by hand.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>
);Like every system notification banner, it needs no live region of its own: see A banner for system notifications. 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 the result of a one-off query, is reloaded on connection.recovered. In React, subscribe with client.on() in an effect, and return the unsubscribe it gives you so the listener goes away with the component.
Event connection.recovered explains when the event fires, with an example that reloads an unread count.
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.connectionRecoveryandconnection.recovered. - System notifications: the lost-connection notification and
useSystemNotifications. - Connection hooks:
useNetworkConnectionState,useNetworkConnectionStateSelector,useWSConnectionStateanduseReportLostConnectionSystemNotification. - Notification hooks:
useSystemNotificationsanduseNotificationApi. - System notification banner: a complete banner.
- Configuration tree: the
wsConnection,networkConnectionandconnectionRecoverysettings.