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

Connection status

When the client can't reach Stream, the SDK says so: "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 change the status the app shows.

What users get

The user's phone loses its signal on a train:

  1. The device reports that it's offline. The open channel's message list shows "Waiting for network..." in a bar at its top, and the channel list shows "Reconnecting...".
  2. With accessibility turned on, a screen reader says "Offline".
  3. The signal comes back, and so does the connection. The bars go away, and the screen reader says "Connected".
  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 network 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, both lists show "Reconnecting..." until it does, and the screen reader says "Reconnecting".

The channel list shows no bar while it loads its first page. When a load fails after the first page has loaded and the connection is fine, it shows "Error while loading, please reload/refresh" in the same place, and tapping it loads the list again.

How it works across the layers

  • State. 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. Chat installs netInfoStatusReporter, which reports the device's network from @react-native-community/netinfo. After a reconnect, client.connectionRecovery reloads the channel lists that have loaded, the active channels and the active threads, and dispatches connection.recovered once the channels and threads have reloaded. Connection explains both stores, the reporter and recovery.
  • UI. MessageList and MessageFlashList render NetworkDownIndicator at their top, in a channel and in a thread. ChannelList renders ChannelListHeaderNetworkDownIndicator at its top. Channel and Thread each render a NotificationAnnouncer, which tells screen readers when the connection changes. All three read the two stores through the SDK's hooks.
  • Navigation. Not involved. The bars and the announcements are the same on every screen.

Chat also closes the socket when the app goes to the background, and opens it again when the app comes back. So every return to the foreground is a reconnect, and recovery runs each time. Closing the socket in the background explains it.

After a reconnect, the components re-render from the stores recovery reloads. Channel marks the channel it shows active, and Thread marks its thread active, so an open channel or thread always reloads.

When a bar shows

NetworkDownIndicator decides by itself whether to render:

  • "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;
  • nothing otherwise.

ChannelListHeaderNetworkDownIndicator only renders its text. ChannelList renders it in the same two cases, and the text is "Reconnecting..." in both.

The socket's health with the delay applied comes from useSettledWSConnectionHealth(). It returns false once a drop has lasted the delay, or at once when the device reports it's offline, and true as soon as the socket is back. A first connection that hasn't finished reads as healthy until the same delay has passed.

Turning it on or off

The connection status is always on, for every user, and there's no capability or dashboard setting for it. What you can change:

  • client.wsConnection.offlineNotificationDisplayDelayMs in client.config: how long a socket drop lasts before "Reconnecting..." shows. 0 shows it at once. The hooks read it from the client's configuration, so a change applies at once.
  • client.connectionRecovery.enabled in client.config: whether the client reloads lists, channels and threads after a reconnect. With it off, connection.recovered isn't dispatched either.
  • announceConnectionState in the accessibility prop of OverlayProvider: whether screen readers hear the changes. It's on by default once accessibility is on.
  • closeConnectionOnBackground on Chat: whether the socket closes in the background.
client.config.set({
  client: { wsConnection: { offlineNotificationDisplayDelayMs: 2000 } },
});

Configuration explains client.config, and Connection the other settings.

To show no bar at all, replace both indicators with components that render nothing, as below.

Customizing

Replacing the indicators

NetworkDownIndicator and ChannelListHeaderNetworkDownIndicator are override keys. Neither receives props. MessageList always renders NetworkDownIndicator, so a replacement decides itself whether to show and where: the default sits absolutely positioned at the top of the list. ChannelList renders its indicator only while the connection is down, in a container at the top of the list.

This replacement shows its own copy in the message list, and nothing in the channel list:

import type { PropsWithChildren } from "react";
import { StyleSheet, Text, View } from "react-native";
import type { NetworkConnectionState } from "stream-chat";
import {
  useNetworkConnectionStateSelector,
  useSettledWSConnectionHealth,
  WithComponents,
} from "stream-chat-react-native";
import type { ComponentOverrides } from "stream-chat-react-native";

// Declared at module scope, so the subscription stays the same across renders.
const selectIsOnline = ({ isOnline }: NetworkConnectionState) => ({ isOnline });

const ConnectionBar = () => {
  const { isOnline } = useNetworkConnectionStateSelector(selectIsOnline) ?? {};
  const isConnected = useSettledWSConnectionHealth();

  // `=== false`: `undefined` means the network status is unknown, not offline.
  if (isOnline !== false && isConnected) return null;

  return (
    <View style={styles.bar}>
      <Text style={styles.text}>
        {isOnline === false ? "You're offline" : "Connecting to chat..."}
      </Text>
    </View>
  );
};

const overrides: ComponentOverrides = {
  ChannelListHeaderNetworkDownIndicator: () => null,
  NetworkDownIndicator: ConnectionBar,
};

export const WithConnectionBar = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);

const styles = StyleSheet.create({
  bar: {
    backgroundColor: "#fff3cd",
    left: 0,
    padding: 8,
    position: "absolute",
    right: 0,
    top: 0,
  },
  text: { color: "#664d03", textAlign: "center" },
});

The copy is plain English here. Pass it through t() with keys of your own to translate it, as Localization explains.

A status of your own

The same hooks work anywhere inside Chat, for example in a screen header:

  • useNetworkConnectionState() and useNetworkConnectionStateSelector(selector) read client.networkConnection.state;
  • useWSConnectionState() and useWSConnectionStateSelector(selector) read client.wsConnection.state as it is, with every drop the moment it happens;
  • useSettledWSConnectionHealth() returns the socket's health with the delay applied, as described in When a bar shows.

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 a selector at module scope, because an inline one resubscribes on every render. Reading state in components explains why.

Styling and texts

The theme styles the default bars: messageList.errorNotification and messageList.errorNotificationText for NetworkDownIndicator, and channelListHeaderErrorIndicator.container and channelListHeaderErrorIndicator.errorText for ChannelListHeaderNetworkDownIndicator. See Theming.

The texts are the translation keys common.waitingForNetwork.text and common.reconnecting.text. Override them as Localization explains.

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 loaded again on connection.recovered:

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

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 <Text>{totalUnread}</Text>;
};

connection.recovered explains when the event fires.

What screen readers hear

With accessibility on, the NotificationAnnouncer in each Channel and Thread announces each change of the socket's settled health:

  • "Offline" when it goes down while the device reports it's offline;
  • "Reconnecting" when it goes down with the network up or unknown;
  • "Connected" when it comes back.

It doesn't announce the state it finds when it mounts, only the changes after that. The texts are the translation keys a11y.connection.offline.accessibilityLabel, a11y.connection.reconnecting.accessibilityLabel and a11y.connection.connected.accessibilityLabel. To turn the announcements off, pass announceConnectionState: false:

import type { PropsWithChildren } from "react";
import { OverlayProvider } from "stream-chat-react-native";

// Declared at module scope, so the provider gets the same object on every render.
const accessibility = { announceConnectionState: false, enabled: true };

export const AppOverlayProvider = ({ children }: PropsWithChildren) => (
  <OverlayProvider accessibility={accessibility}>{children}</OverlayProvider>
);

The bars themselves aren't live regions, so the announcer is the only thing a screen reader hears about the connection.

Reference

  • Connection: client.wsConnection, client.networkConnection, netInfoStatusReporter, client.connectionRecovery and connection.recovered.
  • Connection hooks: useNetworkConnectionState, useNetworkConnectionStateSelector, useWSConnectionState, useWSConnectionStateSelector and useSettledWSConnectionHealth.
  • Indicators: NetworkDownIndicator and ChannelListHeaderNetworkDownIndicator.
  • ComponentsContext: the NetworkDownIndicator, ChannelListHeaderNetworkDownIndicator and ChannelListHeaderErrorIndicator override keys.
  • Chat: the closeConnectionOnBackground prop.
  • OverlayProvider: the accessibility prop.
  • Configuration tree: the wsConnection, networkConnection and connectionRecovery settings.