State and entity hooks
These hooks give a component the client, the channel or the thread it renders in, and a way to read any store. They're exported from stream-chat-react.
How to use them, the useStateStore rules and the pitfalls are on Reading state in components. What a store is, and which objects have one, is on Reactive state.
Each entry gives the signature, then:
- Returns: what the hook gives back.
- Reads: the store or the context it reads.
- Re-renders: what makes the component render again.
- Needs: where the component must sit, and what the hook does outside that place.
useStateStore
Subscribes to one store and returns the part of its value that a selector picks.
import type { StateStore } from "@stream-io/state-store";
type Selection = Readonly<Record<string, unknown> | Readonly<unknown[]>>;
declare function useStateStore<
T extends Record<string, unknown>,
O extends Selection,
>(store: StateStore<T>, selector: (state: T) => O): O;
declare function useStateStore<
T extends Record<string, unknown>,
O extends Selection,
>(store: StateStore<T> | undefined, selector: (state: T) => O): O | undefined;- Returns: what the selector returns: an object or a tuple.
undefinedwhenstoreisundefined, so a component can passthread?.state. - Reads: the store you pass.
StateStoreis the store type from@stream-io/state-store. - Re-renders: when a value in the selection changes. The hook compares each key of the previous selection with
===. A differentstoreorselectormakes it subscribe again, so define selectors at module scope. - Needs: nothing. It works outside
Chatand during server rendering.
The unread thread count has no hook of its own. Read it from client.threads.state:
import type { ThreadManagerState } from "stream-chat";
import { useChatContext, useStateStore } from "stream-chat-react";
const unreadSelector = (state: ThreadManagerState) => ({
unreadThreadCount: state.unreadThreadCount,
});
export const UnreadThreadsBadge = () => {
const { client } = useChatContext();
const { unreadThreadCount } = useStateStore(
client.threads.state,
unreadSelector,
);
if (!unreadThreadCount) return null;
return <span className="unread-badge">{unreadThreadCount}</span>;
};useChatContext
Returns what Chat provides.
useChatContext(): ChatContextValue- Returns:
client: the client passed toChat.channelManager:client.channelManager, which holds the channel lists.searchController: the controller passed toChat, or the oneChatcreates.mutes: the users the current user has muted.getAppSettings(): the app settings, requested once and then reused.theme,customClasses,useImageFlagEmojisOnWindowsandisMessageAIGenerated: theChatprops of the same names.
- Reads: React context. It subscribes to no store, so reading a getter such as
client.userfrom it doesn't re-render the component when the value changes. - Re-renders: when
Chatprovides a new value, for example after the connected user changes or the number of mutes changes. A change that keeps the number of mutes the same doesn't provide a new value. - Needs:
Chat. Throws outside it.
The props are on ChatContext.
useChannel
Returns the channel the component renders in.
useChannel(): Channel- Returns: inside a
Thread, the thread's channel. Otherwise the channel of the nearestChannel. The thread comes first, so aThreadrendered beside aChannel, as in aChatViewlayout, still gets its own channel. - Reads: the
ThreadandChannelcontexts. No store. - Re-renders: when the
ThreadorChannelabove switches to another thread or channel. Changes to the channel's state don't re-render it: read them withuseStateStore(channel.state, selector). - Needs: a
Channelor aThread. Throws outside both.
Inside a Thread, channel.messagePaginator and channel.messageComposer are the channel's, not the thread's. The right owner inside threads says which to read instead. The channel's state is on What channels and threads hold.
useChannelInstanceContext
Returns the channel of the nearest Channel, without throwing.
useChannelInstanceContext(): Partial<ChannelInstanceContextValue>- Returns:
{ channel }inside aChannel,{}outside one. - Reads: the
Channelcontext only. It doesn't look at threads: in aThreadthat isn't rendered inside aChannel, it returns{}. - Re-renders: when the
Channelabove switches to another channel. - Needs: nothing. Never throws.
Use it in a component that renders both inside and outside a channel, as in Context hooks that throw.
useThreadContext
Returns the thread the component renders in.
useThreadContext(): Thread | undefined- Returns: the thread of the nearest
Thread, orundefined. - Reads: the
Threadcontext. No store. - Re-renders: when the
Threadabove switches to another thread. - Needs: nothing. Returns
undefinedoutside aThread.
stream-chat-react has no hook for a thread's state. Read thread.state with useStateStore; its fields are on What channels and threads hold and Thread and thread manager.
import type { ThreadState } from "stream-chat";
import { useStateStore, useThreadContext } from "stream-chat-react";
const replyCountSelector = (state: ThreadState) => ({
replyCount: state.replyCount,
});
export const ReplyCount = () => {
const thread = useThreadContext();
const { replyCount } = useStateStore(thread?.state, replyCountSelector) ?? {};
if (replyCount === undefined) return null;
return <span>{replyCount} replies</span>;
};useThreadListItemContext
Returns the thread of the ThreadList row being rendered.
useThreadListItemContext(): Thread | undefined- Returns: the row's thread, or
undefinedoutside a row. - Reads: the context
ThreadListItemsets for its row. No store. - Re-renders: when the row gets another thread.
- Needs: a
ThreadListItem, whichThreadListrenders for each thread. Returnsundefinedoutside one.
Use it in your own ThreadListItemUI:
import type { ThreadState } from "stream-chat";
import {
useChatContext,
useStateStore,
useThreadListItemContext,
useWorkspaceNavigation,
} from "stream-chat-react";
const rowSelector = (state: ThreadState) => ({
read: state.read,
title: state.title,
});
export const CompactThreadListItem = () => {
const { client } = useChatContext();
const thread = useThreadListItemContext();
const { openThread } = useWorkspaceNavigation();
const { read, title } = useStateStore(thread?.state, rowSelector) ?? {};
if (!thread) return null;
const unread = client.userID
? (read?.[client.userID]?.unreadMessageCount ?? 0)
: 0;
return (
<button onClick={() => openThread(thread)} type="button">
{title}
{unread > 0 && <strong> {unread}</strong>}
</button>
);
};Pass it to WithComponents as ThreadListItemUI, as Overriding components describes.
useIsUserMuted
Says whether the current user has muted a user.
useIsUserMuted(targetUserId?: string): boolean- Returns:
truewhen the current user has muted that user.falsewithout a user ID. - Reads:
mutesfromuseChatContext().Chatfills that list from the user's mutes when the user connects, and replaces it onnotification.mutes_updated. - Re-renders: when
Chatprovides a new value, which happens when the number of mutes changes. - Needs:
Chat. Throws outside it.
The client keeps the same list in a store, client.mutedUsersStore, and updates it on every change. To read it, select its mutedUsers field with useStateStore.
useChat
Runs the setup Chat needs for a client.
useChat(params: UseChatParams): { getAppSettings: () => ReturnType<StreamChat["getAppSettings"]>; mutes: UserMuteResponse[] }It does three things:
- Adds
stream-chat-reactto the client's user agent. - Starts the event handling of the channel lists, threads, polls and reminders, and the reminder timers. It stops them on unmount.
- Keeps the list of muted users: from the user's mutes at first, then from
notification.mutes_updated.
- Returns:
getAppSettings(), which requests the app settings once and then returns the same promise, andmutes. - Reads: no store. It listens for
notification.mutes_updated. - Re-renders: when
muteschanges. - Needs: nothing. It takes the client as a parameter.
Chat calls it already. Call it only in a component that replaces Chat. Under a Chat it starts the same handling again, and when it unmounts it stops the thread, poll and reminder handling that Chat started.
Stores without a hook
Read these stores with useStateStore and your own selector:
| To read | Store and field |
|---|---|
| Muted users | client.mutedUsersStore, mutedUsers |
| Blocked user IDs | client.blockedUsers, userIds |
| The unread thread count, unseen threads | client.threads.state, unreadThreadCount and unseenThreadIds |
| A channel's settings in effect | channel.configState |
| A composer's settings in effect | messageComposer.configState |
| A thread's state | thread.state, with the thread from useThreadContext() |
| A poll's state | poll.state, with the poll from usePollContext() |
The settings stores are explained on Configuration. The connected user (client.user) has no store yet: read it when you need it, or listen for the events that change it.