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.
Chatprovides the client,Channelprovides a channel, andThreadprovides a thread. Inside those, narrower providers provide one item:Messageprovides its message to the message UI,Pollprovides its poll,ChannelListandThreadListprovide their list, and each thread list row provides its thread. - As a prop.
ChannelListrenders theChannelPaginatoryou pass it,ChannelandThreadtake 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.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.
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
markReadOnMountisfalse, 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,
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 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
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 | 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
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's composer or messages inside a thread. Inside a
Thread,useChannel()returns the thread's channel, sochannel.messagePaginatorandchannel.messageComposerare the channel's, not the thread's. Read the composer withuseMessageComposer(), the message paginator withuseMessagePaginator(), and the thread withuseThreadContext(), so the component works in both places. - Calling a throwing hook outside its provider.
useChannel()throws outside aChannelor aThread, and so do the hooks built on it, such asuseMessagePaginator().useChatContext()throws outsideChat. 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 asuseChannelPreviewDisplayName(channel)anduseChannelMemberCount(channel)accept it as an argument. - 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: