Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

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. undefined when store is undefined, so a component can pass thread?.state.
  • Reads: the store you pass. StateStore is 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 different store or selector makes it subscribe again, so define selectors at module scope.
  • Needs: nothing. It works outside Chat and 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 to Chat.
    • channelManager: client.channelManager, which holds the channel lists.
    • searchController: the controller passed to Chat, or the one Chat creates.
    • mutes: the users the current user has muted.
    • getAppSettings(): the app settings, requested once and then reused.
    • theme, customClasses, useImageFlagEmojisOnWindows and isMessageAIGenerated: the Chat props of the same names.
  • Reads: React context. It subscribes to no store, so reading a getter such as client.user from it doesn't re-render the component when the value changes.
  • Re-renders: when Chat provides 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 nearest Channel. The thread comes first, so a Thread rendered beside a Channel, as in a ChatView layout, still gets its own channel.
  • Reads: the Thread and Channel contexts. No store.
  • Re-renders: when the Thread or Channel above switches to another thread or channel. Changes to the channel's state don't re-render it: read them with useStateStore(channel.state, selector).
  • Needs: a Channel or a Thread. 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 a Channel, {} outside one.
  • Reads: the Channel context only. It doesn't look at threads: in a Thread that isn't rendered inside a Channel, it returns {}.
  • Re-renders: when the Channel above 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, or undefined.
  • Reads: the Thread context. No store.
  • Re-renders: when the Thread above switches to another thread.
  • Needs: nothing. Returns undefined outside a Thread.

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 undefined outside a row.
  • Reads: the context ThreadListItem sets for its row. No store.
  • Re-renders: when the row gets another thread.
  • Needs: a ThreadListItem, which ThreadList renders for each thread. Returns undefined outside 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: true when the current user has muted that user. false without a user ID.
  • Reads: mutes from useChatContext(). Chat fills that list from the user's mutes when the user connects, and replaces it on notification.mutes_updated.
  • Re-renders: when Chat provides 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:

  1. Adds stream-chat-react to the client's user agent.
  2. Starts the event handling of the channel lists, threads, polls and reminders, and the reminder timers. It stops them on unmount.
  3. 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, and mutes.
  • Reads: no store. It listens for notification.mutes_updated.
  • Re-renders: when mutes changes.
  • 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.