Providers and context hooks

Components in the UI layer don't fetch or keep chat data. They read it from objects in the state layer, and get those objects in two ways:

  • From the nearest provider above them. Chat provides the client, Channel provides a channel, and Thread provides a thread. Inside those, narrower providers provide one item: Message provides its message to the message UI, Poll provides its poll, ChannelList and ThreadList provide their list, and each thread list row provides its thread.
  • As a prop. ChannelList renders the ChannelPaginator you pass it, Channel and Thread take the channel and thread they provide, and each channel list row gets its channel as a prop.

Use this page to know which channel or thread a component works on, and to read the same objects in your own components.

Take these two screens:

import type {
  Channel as StreamChannel,
  Thread as StreamThread,
} from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
  Thread,
} from "stream-chat-react-native";

export const ChannelScreen = ({ channel }: { channel: StreamChannel }) => (
  <Channel channel={channel}>
    <MessageList />
    <MessageComposer />
  </Channel>
);

export const ThreadScreen = ({ thread }: { thread: StreamThread }) => (
  <Thread thread={thread}>
    <MessageList />
    <MessageComposer />
  </Thread>
);

The two MessageLists are the same component. The first renders channel.messagePaginator, because the nearest owner is the channel. The second renders thread.messagePaginator, with the parent message above the replies, because the nearest owner is the thread. The two MessageComposers work the same way: the first writes into channel.messageComposer, the second into thread.messageComposer. No prop says which one to use.

OverlayProvider and Chat

OverlayProvider goes around your screens, above Chat. It hosts what the SDK draws over everything else: the message menu that opens on a long press, and the image gallery. A screen presented in a separate native window, such as a native modal on iOS, needs its own OverlayProvider; see Chat in modals.

Chat goes around every other SDK component. It takes a connected client, such as the one useCreateChatClient returns (see Quick start).

It provides:

What Read it with
the client, getAppSettings() for the app's settings, and isMessageAIGenerated useChatContext()
translations, from the i18nInstance you pass or a default English one useTranslationContext()
the theme, from the style you pass merged over the default theme useTheme()

Every prop is on Chat, and every field of the context on ChatContext.

While it's mounted, it also:

  • starts the event subscriptions of client.threads, client.polls and client.reminders, and the reminders' timers, so the thread list, polls and reminders follow events. It stops them when it unmounts. Each ChannelList starts the subscriptions of client.channelManager while it's mounted.
  • reports the device's network state to the client, and closes the WebSocket while the app is in the background and opens it again in the foreground, unless closeConnectionOnBackground is false. See Connection.
  • with enableOfflineSupport, opens the local database for the connected user, and renders ChatLoadingIndicator until it's ready. See Offline support.

Chat doesn't connect the user, load channels or decide which screen shows what.

OverlayProvider provides translations and a theme as well, for the overlays it draws, and the setting of the accessibility layer. Its props are on Overlays and bottom sheets.

Channel and Thread

Channel binds one channel to the components inside it. It takes a channel, and configuration props for the message list, the messages and the composer below it:

  • It loads the channel. When it mounts, it watches the channel unless the channel is already watched and loaded with its latest messages, so a channel from a channel list opens without a request. Set initializeOnMount={false} to watch it yourself. A channel without an id, created from its members, is left alone: watch it yourself, or render it as is and create it when the first message is sent.
  • It shows the newest messages. A channel whose message list was left on an older page, for example after a jump to a quoted, pinned or searched message, opens on its newest messages again. A jump that already landed or is still loading, and the jump to the first unread message, decide where the list opens instead. initializeOnMount={false} turns this off too. See The Channel component.
  • It sets the page size of the channel's message list to 25 messages, unless you set one in client.config (see Message paginators).
  • It activates the channel while it's mounted. An active channel stays loaded and is reloaded after a reconnect. See Entity lifecycle.
  • It marks the channel read when it opens with unread messages, unless markReadOnMount is false, and again after a reconnect when the list shows the latest messages, while connection recovery is on.
  • It follows a superseded channel. When the channel you pass is replaced by another object for the same conversation, Channel renders the replacement (see Superseded channels).

Thread binds one thread. It takes a thread, and the same configuration props as Channel:

  • Get the thread from the client, from onThreadSelect on MessageList and ThreadList, or with client.threads.ensure({ channel, parentMessage }). Don't construct one with new Thread(). When the client already holds a thread for that parent message, a second instance stays unmanaged and receives no events.
  • It loads the thread. It loads the first page of replies when none are loaded, and reloads the thread while its state is stale.
  • It shows the newest replies. A thread whose replies were left on an older page, for example after a jump to a reply, opens on its newest replies again, unless a jump to a reply decides where it opens.
  • It activates the thread while it's mounted. An active thread marks new replies read as they arrive, and is reloaded after a reconnect.
  • It doesn't need a Channel around it. Render it on its own screen. Inside a Thread, useChannel() returns the thread's channel.
  • It takes its own configuration. Props set on a Channel don't reach a Thread, so pass the same ones, such as keyboardVerticalOffset, supportedReactions or messageActions, to both.

Which object each part of a channel or thread holds is on What channels and threads hold.

Contexts

Your own components read the same objects through context hooks. This one sits inside a Channel or a Thread:

import { Text } from "react-native";
import {
  useChannel,
  useChatContext,
  useThreadContext,
} from "stream-chat-react-native";

export const WhereAmI = () => {
  const { client } = useChatContext();
  const channel = useChannel();
  const { thread } = useThreadContext();

  return (
    <Text>
      {client.userId} is in {channel.cid}
      {thread ? `, in the thread of message ${thread.id}` : ""}
    </Text>
  );
};
Hook Returns Provided by Outside its provider
useChatContext() client, getAppSettings, isMessageAIGenerated Chat throws
useChannel() the channel: the thread's channel inside a Thread, otherwise the Channel's Channel or Thread throws
useChannelContext() channel and the channel's display options Channel or Thread throws
useThreadContext() { thread, allowThreadMessagesInChannel } Thread returns an object whose thread is undefined
useMessagePaginator() the message list's paginator: the thread's inside a Thread, otherwise the channel's Channel or Thread throws
useMessageComposer() the composer: the edit composer, the thread's inside a Thread, otherwise the channel's Channel or Thread throws
useMessageContext() the message, its handlers and display options for one message Message, once for each message a list renders returns {}
usePollContext() { message, poll } Poll, for a message that carries a poll throws
useChannelsContext() the channel list's paginator, and the props set on ChannelList ChannelList throws
useThreadsContext() the thread list's threads, loading flags, onThreadSelect and loadMore ThreadList throws
useThreadListItemContext() the thread of one thread list row, with its channel, parent message, latest reply and unread count each row a thread list renders returns {}
useTranslationContext() t, tDateTimeParser, userLanguage Chat and OverlayProvider throws
useTheme() { theme } Chat and OverlayProvider throws
useComponentsContext() every override key, with the overrides in effect merged over the defaults WithComponents returns the defaults

useMessageContext() is how a custom message UI reads its message, because the SDK passes it no props. See Message UI and Overriding components.

Which owner a hook reads

The context hooks find their owner from where the component sits, so the same component works in a channel and in a thread:

Hook Inside a Channel Inside a Thread
useChannel() the channel the thread's channel
useThreadContext() { thread: undefined } { thread }
useMessageComposer() channel.messageComposer thread.messageComposer
useMessagePaginator() channel.messagePaginator thread.messagePaginator

While the user edits a message, useMessageComposer() returns the composer that edits it instead, in both places.

useMessagePaginator() picks the message paginator the way the SDK's own message lists do: the thread's when there is one, otherwise the channel's.

The objects these hooks return hold their data in stores. Rendering from a store, and the rules that keep it current, are on Reading state in components.

Pitfalls

  • Wrapping Thread in a Channel. It doesn't need one. Render it on its own screen, or next to Channel on the same screen.
  • Setting props on Channel only. A Thread doesn't inherit them. Pass the same configuration props to both.
  • Reading the channel's composer or messages inside a thread. Inside a Thread, useChannel() returns the thread's channel, so channel.messagePaginator and channel.messageComposer are the channel's, not the thread's. Read the composer with useMessageComposer(), the message paginator with useMessagePaginator(), and the thread with useThreadContext(), so the component works in both places.
  • Calling a throwing hook outside its provider. useChannel() throws outside a Channel or a Thread, and so do the hooks built on it, such as useMessagePaginator(). useChatContext() throws outside Chat. Render the component inside the provider. A channel list row takes its channel as a prop, so a component that renders there reads the channel from its props, and the channel hooks such as useChannelPreviewDisplayName(channel) and useChannelMemberCount(channel) accept it as an argument.
  • Creating a thread with new Thread. Use client.threads.ensure(), so the thread is the one instance the client keeps up to date.

Used in

Every page that renders state, including: