# State Overview

Stream Chat for React Native uses the [Stream Chat client](https://github.com/GetStream/stream-chat-js) to connect to and communicate with the Stream API.

See the full [JavaScript client docs](/chat/docs/javascript/) for client details.

## Best Practices

- Use `StreamChat.getInstance()` once and reuse it to avoid multiple WebSockets.
- Connect the user once and disconnect explicitly when they sign out.
- Prefer UI-context helpers (e.g., `useMessageInputContext`) over direct client calls.
- Keep `Chat` near the app root so contexts stay available across screens.
- Avoid creating channels with only members if you need to edit membership later.

## Setup

To interact with the Stream Chat API you must create a client instance and connect to the API, usually as an authenticated user.

### Instantiation

`StreamChat` is a dependency of `stream-chat-react-native` and can be imported from `stream-chat`. Create a client with `getInstance` and an API key.

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

const client = StreamChat.getInstance("api_key");
```

<admonition type="warning">

**Usage of `StreamChat.getInstance()` available since stream-chat@2.12.0.**

<br />
This singleton pattern lets you instantiate a single client and reuse it across the app. After calling it once, any subsequent <code>
  getInstance
</code> call will return the initial StreamChat instance. This will prevent you from accidentally creating multiple
StreamChat instances, opening multiple WebSockets, and driving up your concurrent connections unnecessarily.
<br />
<br />

Stream Chat is backward compatible. You can still use <code>new StreamChat()</code> if needed.

<br />

```ts
const client = new StreamChat("api_key");
```

Calling <code>new StreamChat()</code> repeatedly creates new clients and WebSocket connections when <code>connectUser</code> is called.
If you are using <code>new StreamChat()</code> you need to be vigilant about your use to ensure you are not creating multiple WebSocket connections unnecessarily.

</admonition>

### Connecting a User

The recommended way to create a client and connect a user is with the `useCreateChatClient` hook:

```tsx
import {
  useCreateChatClient,
  Chat,
  OverlayProvider,
} from "stream-chat-react-native";

const App = () => {
  const chatClient = useCreateChatClient({
    apiKey: "YOUR_API_KEY",
    userData: { id: "testUser", name: "Test User" },
    tokenOrProvider: "user_token",
  });

  if (!chatClient) {
    return null; // Show loading state
  }

  return (
    <OverlayProvider>
      <Chat client={chatClient}>{/* Your app */}</Chat>
    </OverlayProvider>
  );
};
```

Alternatively, you can manually call `connectUser` on the client. The `user_token` is typically sent from your back end when a user is authenticated.

```ts
await client.connectUser(
  {
    id: "testUser",
    name: "Test User",
  },
  "user_token",
);
```

Avoid repeated `connectUser` calls; reconnecting without disconnecting will warn and can throw.

## UI Components

After connecting a user, the UI components handle most client interactions for you.

### Providing the Client to the UI

Provide the client by passing it to `Chat` as the `client` prop.

```tsx
import { StreamChat } from "stream-chat";
import { Chat } from "stream-chat-react-native";

const client = StreamChat.getInstance("api_key");

export const Screen = () => (
  <Chat client={client}>{/** App components */}</Chat>
);
```

The UI components access the client via [`context`](https://reactjs.org/docs/context.html).

<admonition type="note">

If you are customizing certain components or functionality you may have to interact with the client as well.
You can access the client provided to the `Chat` component internally via the `useChatContext`.

<br />

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

const { client } = useChatContext();
```

</admonition>

### Using UI Functions

The UI exposes functions that keep state in sync via `context`. See the [contexts section](/chat/docs/sdk/react-native/v8/contexts/attachment-picker-context/). Use these functions to keep UI state up to date.

The `sendMessage` function for instance, provided by `useMessageInputContext`, is not the same as the `sendMessage` function found directly on a `channel` in the client.
Therefore calling `channel.sendMessage(messageData)` will not result in a message optimistically showing in the UI, or a failed send state, instead the message will not show until it is returned by the server.

<admonition type="warning">

You should not assume any function directly called on the client will result in a UI update.
The UI state is managed internally by the components and `context`, most client interactions require an event returned by the server to update the UI.

</admonition>

## Accessing the Client Instance

There are multiple ways to access the client. [As mentioned](#providing-the-client-to-the-ui), you can use `useChatContext` inside `Chat`.
This works well if you can wrap your entire application in a single `Chat` component and have the `StreamChat` instance provided throughout your app via the internal `context`.
But if you have multiple screens that contain `Chat` components where a client instance is necessary you will need to access the shared instance in other ways.

You can also store the client in your own context or class, or rely on the singleton via `getInstance`.

<admonition type="warning">

Do not create and connect multiple instances using `new StreamChat()`, this will result in multiple `StreamChat` instances and opening multiple WebSocket connections.

</admonition>

## Direct Interaction

There may be some direct interaction with the client that is required for your application.
Referring to the [full documentation](/chat/docs/javascript/) is suggested for detailed information on client functionality.
Common interactions with the client used in conjunction with the Stream Chat for React Native components have been included for convenience.

### Creating a Channel

A channel must be initialized with either an `id` or a list of members. If you pass members, the backend auto-generates an `id`.

<admonition type="note">

You can't add or remove members from a channel created using a members list.

</admonition>

<tabs>

<tabs-item value="channelId" label="Channel Id">

```ts
/**
 *  Channel created using a channel id
 */
const channel = client.channel(channel_type, "channel_id", {
  name: "My New Channel",
});
```

</tabs-item>

<tabs-item value="members" label="Members List">

```ts
/**
 *  Channel created using a members list
 */
const channel = client.channel(channel_type, {
  members: ["userOne", "userTwo"],
  name: "My New Channel",
});
```

</tabs-item>

</tabs>

To create a channel on the server, call `create` or `watch` on the channel instance.
`create` will create the channel while `watch` will both create the channel and subscribe the client to updates on the channel.

<tabs>

<tabs-item value="watch" label="watch">

```ts
await channel.watch();
```

</tabs-item>

<tabs-item value="create" label="create">

```ts
await channel.create();
```

</tabs-item>

</tabs>

### Querying ChannelList

To show a list of channels, see [Query Channels](/chat/docs/javascript/query_channels/).

Internally, a client has `queryChannels` API that can be used to get the list of channels.

```ts
const filter = { type: "messaging", members: { $in: ["thierry"] } };
const sort = [{ last_message_at: -1 }];

const channels = await chatClient.queryChannels(filter, sort, {
  watch: true, // this is the default
  state: true,
});
```

### Sending messages

To send a message in the channel, you can refer to [the following guide](/chat/docs/javascript/send_message/).

Internally, a channel instance has `sendMessage` API that has to be called to send a message, as below:

```ts
const message = await channel.sendMessage({
  text: "Hey there.",
});
```

### Disconnecting a User

To disconnect a user you can call `disconnect` on the client.

```ts
await client.disconnectUser();
```

## POJO

With a few of our new features, we've decided to refresh our state management and moved to a subscribable POJO with selector based system to make developer experience better when it came to rendering information provided by our `StreamChat` client.

<admonition type="note">

This change is currently only available within `StreamChat.threads`, `StreamChat.poll` and `StreamChat.polls` but will be reused across the whole SDK later on.

</admonition>

### Why POJO (State Object)

Our SDK holds and provides A LOT of information to our integrators and each of those integrators sometimes require different data or forms of data to display to their users. All of this important data now lives within something we call state object and through custom-tailored selectors our integrators can access the combination of data they require without any extra overhead and performance to match.

### What are Selectors

Selectors are functions provided by integrators that run whenever state object changes. These selectors should return piece of that state that is important for the appropriate component that renders that piece of information. Selectors itself should not do any heavy data computations that could resolve in generating new data each time the selector runs (arrays and objects), use pre-built hooks with computed state values or build your own if your codebase requires it.

#### Rules of Selectors

1. Selectors should return a named object.

```ts
const selector = (nextValue: ThreadManagerState) => ({
  unreadThreadsCount: nextValue.unreadThreadsCount,
  active: nextValue.active,
  lastConnectionDownAt: nextvalue.lastConnectionDownAt,
});
```

2. Selectors should live outside components scope or should be memoized if it requires "outside" information (`userId` for `read` object for example). Not memoizing selectors (or not stabilizing them) will lead to bad performance as each time your component re-renders, the selector function is created anew and `useStateStore` goes through unsubscribe and resubscribe process unnecessarily.

```tsx
// ❌ not okay
const Component1 = () => {
  const { latestReply } = useStateStore(
    thread.state,
    (nextValue: ThreadState) => ({
      latestReply: nextValue.latestReplies.at(-1),
    }),
  );

  return <Text>{latestReply.text}</Text>;
};

// ✅ okay
const selector = (nextValue: ThreadState) => ({
  latestReply: nextValue.latestReplies.at(-1),
});

const Component2 = () => {
  const { latestReply } = useStateStore(thread.state, selector);

  return <Text>{latestReply.text}</Text>;
};

// ✅ also okay
const Component3 = ({ userId }: { userId: string }) => {
  const selector = useCallback(
    (nextValue: ThreadState) => ({
      unreadMessagesCount: nextValue.read[userId].unread_messages,
    }),
    [userId],
  );

  const { unreadMessagesCount } = useStateStore(thread.state, selector);

  return <Text>{unreadMessagesCount}</Text>;
};
```

3. Break your components down to the smallest reasonable parts that each take care of the appropriate piece of state if it makes sense to do so.

### Accessing Reactive State

Our SDK currently allows to access two of these state structures - in [Thread](https://github.com/GetStream/stream-chat-js/blob/master/src/thread.ts) and [ThreadManager](https://github.com/GetStream/stream-chat-js/blob/master/src/thread_manager.ts) instances under `state` property.

#### Vanilla

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

const client = new StreamChat(/*...*/);

// calls console.log with the whole state object whenever it changes
client.threads.state.subscribe(console.log);

let latestThreads;
client.threads.state.subscribeWithSelector(
  // called each time theres a change in the state object
  (nextValue) => ({ threads: nextValue.threads }),
  // called only when threads change (selected value)
  ({ threads }) => {
    latestThreads = threads;
  },
);

// returns lastest state object
const state = client.threads.state.getLatestValue();

const [thread] = latestThreads;

// thread instances come with the same functionality
thread?.state.subscribe(/*...*/);
thread?.state.subscribeWithSelector(/*...*/);
thread?.state.getLatestValue(/*...*/);
```

#### useStateStore Hook

For the ease of use - the React Native SDK comes with the appropriate state access hook which wraps `StateStore.subscribeWithSelector` API for the React-based applications.

```tsx
import { useStateStore } from "stream-chat-react-native";
import type { ThreadManagerState } from "stream-chat";

const selector = (nextValue: ThreadManagerState) =>
  ({ threads: nextValue.threads }) as const;

const CustomThreadList = () => {
  const { client } = useChatContext();
  const { threads } = useStateStore(client.threads.state, selector);

  return (
    <View>
      {threads.map((thread) => (
        <Text key={thread.id}>{thread.id}</Text>
      ))}
    </View>
  );
};
```

## Thread and ThreadManager

One of the feature that follows the new POJO style of state management is the [threads feature](/chat/docs/sdk/react-native/v8/guides/custom-thread-list/).

It provides a reactive state for both a single `Thread` (through a reactive `threadInstance`) and a list of threads (through `StreamChat.threads`).

Both states can be accessed through `selector`s as outlined in the examples above.

## Poll and PollManager

Our new polls feature also follows the new POJO style of state management. A `poll` in itself is something that needs to be linked to a `message` in order for it to work. When a poll is created, the only way to make it visible to the users is to send it as a message. This `message` needs to have a `poll_id` attached to it and preferably no text.

You can access each `poll`'s reactive state by getting it by ID using `StreamChat.polls.fromState(<poll-id>)`.

<admonition type="note">

Please keep in mind that `message.poll` is not going to be reactive, but will rather contain the raw `poll` data as returned by our backend.

</admonition>

### Utility hooks

The React Native SDK provides 2 utility hooks to help with consuming the `poll` state. They can be found listed below:

- [`usePollStateStore`](/chat/docs/sdk/react-native/v8/hooks/poll/use-poll-state-store/)
- [`usePollState`](/chat/docs/sdk/react-native/v8/hooks/poll/use-poll-state/)

Similarly to the `threads` feature, one can also directly use `useStateStore` and access `StreamChat.polls.fromState(<poll-id>).state` through custom `selector`s.

<admonition type="note">

Both `usePollStateStore` and `usePollState` can only be used in children of a [`PollContext`](/chat/docs/sdk/react-native/v8/contexts/poll-context/). This impediment does not exist however on `useStateStore`.

</admonition>

Due to this, all `poll` related components within the SDK are self-wrapped within a `PollContext` and require `message` and `poll` as mandatory props.

#### Example

```tsx
import { usePollState } from "stream-chat-react-native";

const CustomPollComponent = () => {
  const { name, options } = usePollState();

  return (
    <View>
      <Text>{name}</Text>
      {options.map((option) => (
        <Text key={option.id}>{option.text}</Text>
      ))}
    </View>
  );
};

const PollMessage = ({ message }) => {
  const { client } = useChatContext();
  const pollInstance = client.polls.fromState(message?.poll_id);
  return (
    <PollContextProvider value={{ message, poll: pollInstance }}>
      <CustomPollComponent />
    </PollContextProvider>
  );
};
```


---

This page was last updated at 2026-04-17T17:33:45.469Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v8/state-and-offline-support/state-overview/](https://getstream.io/chat/docs/sdk/react-native/v8/state-and-offline-support/state-overview/).