How components connect to state
Components in the UI layer don't fetch or keep chat data. They read it from objects in the state layer, and three components hand those objects down the tree: Chat gives the client, Channel gives a channel, and Thread gives a thread. Every component inside reads from the nearest one.
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:
- the client, through
ChatContext, withgetAppSettings()for the app's settings andisMessageAIGenerated; - translations, through
TranslationContext, from thei18nInstanceyou pass or a default English one; - the theme, through
ThemeContext, from thestyleyou pass merged over the default theme.
While it's mounted, it also:
- starts the event subscriptions of
client.threads,client.pollsandclient.reminders, and the reminders' timers, so the thread list, polls and reminders follow events. It stops them when it unmounts. EachChannelListstarts the subscriptions ofclient.channelManagerwhile 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
closeConnectionOnBackgroundisfalse. See Connection. - with
enableOfflineSupport, opens the local database for the connected user, and rendersChatLoadingIndicatoruntil it's ready. See Offline support.
Chat doesn't connect the user, load channels or decide which screen shows what.
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. - 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
markReadOnMountisfalse, and again after a reconnect when the list shows the latest messages. - It follows a superseded channel. When the channel you pass is replaced by another object for the same conversation,
Channelrenders 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
onThreadSelectonMessageListandThreadList, or withclient.threads.ensure({ channel, parentMessage }). Don't construct one withnew 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 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
Channelaround it. Render it on its own screen. Inside aThread,useChannel()returns the thread's channel. - It takes its own configuration. Props set on a
Channeldon't reach aThread, so pass the same ones, such askeyboardVerticalOffset,supportedReactionsormessageActions, 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 | Gives | 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 { thread: undefined } |
useMessagePaginator() |
the message list's paginator: 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 message list renders |
returns {} |
useTranslationContext() |
t, tDateTimeParser, userLanguage |
Chat and OverlayProvider |
throws |
useMessageContext() is how a custom message component reads its message. See Overriding components.
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
Threadin aChannel. It doesn't need one. Render it on its own screen, or next toChannelon the same screen. - Setting props on
Channelonly. AThreaddoesn't inherit them. Pass the same configuration props to both. - Reading the channel from your own state inside a thread. Read it with
useChannel()and the thread withuseThreadContext(), so the component works in both places. - Creating a thread with
new Thread. Useclient.threads.ensure(), so the thread is the one instance the client keeps up to date.
Used in: every page that renders state, including Channel UI, Thread UI and Message list UI.