# Connection

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

A chat connection can fail in two ways. The device can lose its network, or the client's WebSocket can drop while the network is fine: the server closed it, the token expired, or a health check timed out. The client reports the two separately, so an app can tell "you're offline" from "reconnecting".

When the socket comes back, the client brings what the user is looking at up to date by itself, then announces it with `connection.recovered`.

This page covers the connection status first, then recovery. Showing a lost-connection banner is on [Connection status](https://getstream.io/chat/docs/sdk/react-native/v10/features/app-experience/connection-status/), and reading these stores in components on [Reading state in components](https://getstream.io/chat/docs/sdk/react-native/v10/ui/reading-state-in-components/).

## Connection status

| Question                                  | Where to read it                             | Hook                          |
| ----------------------------------------- | -------------------------------------------- | ----------------------------- |
| Is the WebSocket up?                      | `client.wsConnection.state`, `isHealthy`     | `useWSConnectionState()`      |
| Does the device have a network?           | `client.networkConnection.state`, `isOnline` | `useNetworkConnectionState()` |
| Has the client caught up after reconnect? | the `connection.recovered` event             |                               |

The two stores can disagree either way. A socket can drop on a working network. And when a device loses its network without a reporter telling the client, the socket can look healthy for up to 35 seconds, until its next health check fails. A reported loss marks the socket down at once.

The status isn't an event, so subscribe to the store you mean. In a component, the hooks in the table subscribe for you, and `useWSConnectionStateSelector()` and `useNetworkConnectionStateSelector()` take a selector.

### Socket health

`client.wsConnection.state` holds `{ isHealthy, lastHealthyAt, lastUnhealthyAt }`. `isHealthy` is always a boolean, so `!isHealthy` is safe. `client.wsConnection.isHealthy` reads the current value. `isHealthy` is `false` before the first connection too, and only `lastHealthyAt` being `null` tells that apart from a drop.

The store records every change, including a deliberate `client.closeConnection()`, and it records a drop the moment it happens. Most drops recover in under a second, so a UI that shows every drop at once makes a working app look broken. Wait `offlineNotificationDisplayDelayMs` before showing one, and cancel the wait if the socket comes back:

```ts
const delayMs = client.wsConnection.config.offlineNotificationDisplayDelayMs;
let timer: ReturnType<typeof setTimeout> | undefined;

const unsubscribe = client.wsConnection.state.subscribeWithSelector(
  ({ isHealthy }) => ({ isHealthy }),
  ({ isHealthy }) => {
    clearTimeout(timer);
    if (isHealthy) {
      console.log("Connected");
      return;
    }
    timer = setTimeout(() => console.log("Reconnecting..."), delayMs);
  },
);
```

In a component, `useSettledWSConnectionHealth()` does this. It returns `false` only once a drop has lasted `offlineNotificationDisplayDelayMs`, 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.

The socket's timings are set through `client.config`, which [Configuration](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/configuration/) explains:

```ts
client.config.set({
  client: {
    wsConnection: {
      connectTimeoutMs: 15_000, // how long connecting waits for the server
      healthCheckGracePeriodMs: 10_000, // extra time before a silent socket counts as dead
      offlineNotificationDisplayDelayMs: 5_000, // how long a UI waits before showing a drop
      pingIntervalMs: 25_000, // how often a health check goes out
    },
  },
});
```

The values above are the defaults:

- `pingIntervalMs` can only be lowered: 25 seconds is also the maximum, because a slower ping risks the server closing the connection as idle.
- The socket counts as dead after `pingIntervalMs + healthCheckGracePeriodMs` without an answer, 35 seconds by default. Lowering the ping makes a dead socket noticed sooner.
- `offlineNotificationDisplayDelayMs` is the one setting the client doesn't act on. It's there so every UI waits the same time, and `useSettledWSConnectionHealth()` reads it.
- `enableWSFallback: true` falls back to HTTP long-polling when the WebSocket can't connect, for networks that block WebSockets. The store then follows the long-poll.

### Device network

`client.networkConnection.state` holds `{ isOnline, lastOnlineAt, lastOfflineAt }`, and `client.networkConnection.isOnline` reads the current value.

`isOnline` is `undefined` when the status is unknown: nothing has reported it yet. Test it with `=== false`:

```ts
if (client.networkConnection.isOnline === false) {
  // The device is offline.
}

// Wrong: also true while the status is unknown, such as before the first report.
if (!client.networkConnection.isOnline) {
  // ...
}
```

The client can't detect the network by itself, because every platform reports it differently. A **reporter** tells it. A reporter is a function that reports the current status, reports again on every change, and returns a function that stops it. Without one, the client copies the socket's status, which can't tell a dead socket from a lost network.

The SDK's reporter is `netInfoStatusReporter`. It listens to [`@react-native-community/netinfo`](https://github.com/react-native-netinfo/react-native-netinfo), a required dependency of the SDK, and reports the device online when NetInfo says it's connected and the internet is reachable. Until NetInfo has checked reachability, it reports whether the device is connected to a network.

- **`useCreateChatClient`** installs it when it creates the client, so the status is known from the start.
- **`<Chat>`** installs it on the client it's given, when it mounts. It sets it under `client.networkConnection.statusReporter` in `client.config`, unless a reporter is registered there already, so a reporter of your own is kept.

When you create the client yourself, install it at construction, as `useCreateChatClient` does. Otherwise the client copies the socket's status until `<Chat>` mounts, and records a socket that failed for another reason, such as an expired token, as the device being offline:

```ts
import { StreamChat } from "stream-chat";
import { netInfoStatusReporter } from "stream-chat-react-native";

export const client = new StreamChat("YOUR_API_KEY", {
  client: { networkConnection: { statusReporter: netInfoStatusReporter } },
});
```

Setting the same reporter again does nothing, so `<Chat>` doesn't install it twice. A host with no event to listen to can report the status directly with `client.networkConnection.setStatus(isOnline)`.

Don't hold back requests until the network reports online. Everything works while the status is unknown, and the client doesn't need it. It only helps notice a drop, and a return, sooner.

### Closing the socket in the background

For apps created before 2022-01-18, the server sends push notifications only while the user has no open socket; newer apps get them whether the user is online or not. `<Chat>` closes the socket when the app goes to the background, and opens it again when the app comes back to the foreground. It reacts to the `background` and `active` states of React Native's `AppState`. The `inactive` state on iOS leaves the socket open.

`client.closeConnection()` keeps the user connected to the client, and doesn't reconnect by itself. The socket store records the close as a drop. Reopening counts as a reconnect, so recovery runs, as below.

To keep the socket open in the background, for example while your app plays a call, pass `closeConnectionOnBackground={false}`:

```tsx
<Chat client={client} closeConnectionOnBackground={false}>
  {/* your screens */}
</Chat>
```

To close and open the socket yourself instead, turn the prop off and call the same methods:

```ts
// The app went to the background.
await client.closeConnection();

// The app is back in the foreground.
await client.openConnection();
```

[Push notifications](https://getstream.io/chat/docs/sdk/react-native/v10/features/app-experience/push-notifications/) explains how pushes reach the device while the socket is closed.

## Connection recovery

### What reloads after a reconnect

>
> **Info:** **Example**
>
> The user's phone loses its signal for ten minutes. While it's offline, they miss messages, reactions and read receipts, and the server ends every watch their client held. When the socket comes back, the client:
>
> 1. **Refreshes every channel list registered on `client.channelManager` that has loaded.** A list's paginator is registered with `client.channelManager.insertPaginator()`, as [Channel list paginators](https://getstream.io/chat/docs/sdk/react-native/v10/state/loading-data/channel-list-paginators/) shows. Each list queries every page it has loaded again, from the first, up to 150 channels, and stays on screen meanwhile. Its channels get their latest data and sort position, and the ones no page returned leave the list, such as a channel that was deleted or hidden while the user was offline, or one they left on another device. Channels loaded past the first 150 are dropped and load again when they scroll to them. These requests watch the channels again. A paginator your code created but didn't register isn't refreshed.
> 2. **Reloads every active channel.** An active channel is one something opened with `Channel.activate()`. The channel watches again and refreshes its newest loaded interval of messages, not the older intervals a jump loaded: messages deleted there while the user was away disappear, and their unsent messages stay.
> 3. **Reloads every active thread's replies.**
> 4. **Marks every other thread the client holds as stale.** A stale thread loads again the next time it's opened.
> 5. **Dispatches `connection.recovered`** once the active channels and threads have reloaded. The channel lists refresh alongside them, and can finish later.
>

After that event, the thread list loads again, if it was opened in this session.

A first connection isn't a reconnect, so none of this runs then. The components activate what they show: `<Channel>` its channel and `<Thread>` its thread, so those are the ones that reload. [Entity lifecycle](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/entity-lifecycle/) explains `Channel.activate()` and `Thread.activate()`.

Because `<Chat>` closes the socket in the background, every return to the foreground is a reconnect, and this recovery runs each time.

A refresh that fails isn't reported. A channel list that already shows channels keeps them, and with offline support it also sets no `lastQueryError`.

### With offline support

With [offline support](https://getstream.io/chat/docs/sdk/react-native/v10/state/offline-support/offline-support/), the client first sends what the user did offline, then catches up, then reloads:

1. **The pending tasks are sent.** Messages, edits, deletions, reactions and drafts that failed while the device was offline are sent in the order they were made.
2. **The sync request runs.** The client asks the server for the events it missed since the last sync and writes them to the offline database.
3. **Channel lists refresh, and active channels and threads reload,** as in the steps above. The channel lists wait for the sync before they query the server.
4. **`connection.recovered` is dispatched** once the active channels and threads have reloaded.

`<Chat enableOfflineSupport>` also turns on `persistUserOnConnectionFailure` under the `client` key of `client.config`. When the first connection fails because the device is offline, the client keeps the user and connects once the network is back, instead of disconnecting the user. Without it, a failed `client.connectUser()` disconnects the user, and the app has to call it again.

### Channels re-watched on demand

A watch is what makes the server send a channel's events to this client. The server ties watches to the connection, so a reconnect ends them all. The steps above watch again the channels each registered list loaded again and the active channels.

Other channels the user's client holds keep `watchStatus` set to `wasWatching`: channels a list had loaded beyond the first 150, channels they opened earlier, channels no list shows. Watching them all again could exceed the server's watch limit. Instead, each one is watched again when the channel manager routes it after an event, for example `notification.message_new` when someone writes in it.

### connection.recovered

`connection.recovered` fires after every reconnect while recovery is on: an automatic one, and `client.closeConnection()` followed by `client.openConnection()`, including the reopen when the app comes back to the foreground. It carries nothing but its type.

By the time it fires, the client has reloaded its active channels and threads, and its channel lists are refreshing. `<Channel>` listens for it to mark its channel read when the list is at the newest messages, as [Read state and unread counts](https://getstream.io/chat/docs/sdk/react-native/v10/state/channels-and-threads/read-state-and-unread-counts/#when-the-sdk-marks-a-channel-read) explains. Listen for it to refresh what the client doesn't own: results of one-off queries, counters, your own data.

For example, an app that shows the user's total unread count from a one-off query loads it again:

```ts
let totalUnread = 0;

const loadTotalUnread = async () => {
  const { total_unread_count } = await client.unreadCounts();
  totalUnread = total_unread_count;
};

const { unsubscribe } = client.on("connection.recovered", () => {
  void loadTotalUnread();
});
```

If the socket drops again while the client reloads, the event waits for the next reconnect, whose recovery fires it.

### Turning recovery off or running it by hand

An app that brings its data up to date itself can turn recovery off:

```ts
client.config.set({ client: { connectionRecovery: { enabled: false } } });
```

Then a reconnect refreshes no channel list, reloads no channel or thread, and marks no thread stale. `connection.recovered` isn't dispatched either, so `<Channel>` doesn't mark its channel read after a reconnect. The setting is read at each reconnect, so a change applies from the next one.

To run a recovery yourself, call `client.connectionRecovery.recover()`. It does steps 1 to 5 above, whether or not recovery runs on its own, and dispatches `connection.recovered` at the end. It does nothing while a recovery is already running.

With recovery off, find the reconnect in the socket store: `isHealthy` changes from `false` to `true` while `lastUnhealthyAt` is set. A first connection leaves `lastUnhealthyAt` at `null`:

```ts
let wasHealthy = client.wsConnection.isHealthy;

const unsubscribe = client.wsConnection.state.subscribeWithSelector(
  ({ isHealthy, lastUnhealthyAt }) => ({ isHealthy, lastUnhealthyAt }),
  ({ isHealthy, lastUnhealthyAt }) => {
    const reconnected = isHealthy && !wasHealthy && !!lastUnhealthyAt;
    wasHealthy = isHealthy;
    if (reconnected) void client.connectionRecovery.recover();
  },
);
```

## Used in

- [Connection status](https://getstream.io/chat/docs/sdk/react-native/v10/features/app-experience/connection-status/)
- [Notifications](https://getstream.io/chat/docs/sdk/react-native/v10/features/app-experience/notifications/)
- [Offline support](https://getstream.io/chat/docs/sdk/react-native/v10/state/offline-support/offline-support/)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/state/connection/](https://getstream.io/chat/docs/sdk/react-native/v10/state/connection/).