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

Message hooks

These hooks give a component the message it renders and the handlers of that message: its actions, its operations, its receipts and its reactions. They're exported from stream-chat-react-native and stream-chat-expo.

Message builds the handlers once for the message it renders and puts them in MessageContext. Inside a custom message component, read them from useMessageContext() or useMessageActionHandlers() instead of building them again. The context's values are on MessageContext, and the default actions that call the handlers on Actions. Two more hooks for a message have pages of their own: useMessageReminder on Reminder hooks, and useStreamingMessage on AI hooks.

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.

useMessageContext

Returns the message being rendered, and what Message worked out for it.

useMessageContext(): MessageContextValue
  • Returns, among others:
    • message: the LocalMessage, and channel, its channel.
    • isMyMessage, alignment, groupStyles, lastGroupMessage and showAvatar: where and how the message sits in the list.
    • images, videos, files and otherAttachments: its attachments by kind. reactions and hasReactions: its reactions.
    • messageActionHandlers: the handlers behind the message's actions, the same object useMessageActionHandlers() returns.
    • handleReaction(type): what tapping a reaction does. It calls the handleReaction prop of Channel or Thread, then toggles the reaction. A selectReaction prop on Channel or Thread replaces it. undefined on a failed message or an error message.
    • onOpenThread(), onPress, onLongPress, showMessageOverlay() and dismissOverlay(): what pressing the message does, and the message menu.
  • Reads: the context Message provides. No store.
  • Re-renders: when Message provides a new value, which happens when the message or one of the values worked out from it changes.
  • Needs: a Message. Outside one it returns an empty object, typed as the full value, so check message before you use it there.

useMessagesContext

Returns the message settings and handlers set on Channel or Thread.

useMessagesContext(): MessagesContextValue
  • Returns: the props of Channel and Thread that configure messages: the handle* callbacks, messageActions, supportedReactions, myMessageTheme, messageContentOrder and the rest.
  • Reads: the context Channel and Thread provide. No store.
  • Re-renders: when the channel changes, or one of a few of those props does, such as messageContentOrder, myMessageTheme or the number of supportedReactions. A new handle* callback or messageActions function after mount doesn't provide a new value.
  • Needs: a Channel or a Thread. Throws outside both.

The values are listed on MessagesContext.

useMessageActionHandlers

Returns the handlers behind the actions of the message being rendered.

useMessageActionHandlers(): MessageActionHandlerCallbacks
  • Returns: one handler for each action. They act on the message in context and take no message argument:

    Handler What it does
    handleCopyMessage() Copies the text, translated into the user's language when a translation exists, and publishes a clipboard:message:copy:* notification. Does nothing without text.
    handleDeleteMessage() Asks for confirmation, then deletes the message and publishes an api:message:delete:* notification.
    handleDeleteForMeMessage() Deletes the message for the current user only. No confirmation and no notification. Rejects when the request fails.
    handleEditMessage() Starts editing the message in the composer, once the keyboard is open.
    handleQuotedReplyMessage() Sets the message as the composer's quoted message.
    handleResendMessage() Sends a failed message again. Rejects when the retry fails.
    handleFlagMessage() Asks for confirmation, then flags the message with client.moderation.flagMessage({ message_id }) and publishes an api:message:flag:* notification.
    handleMarkUnreadMessage() Marks the channel unread from the message on, with Channel.markUnread({ message_id }), and publishes an api:message:markUnread:* notification.
    handleTogglePinMessage() Pins the message, or unpins it when it's pinned, and publishes an api:message:pin:* or api:message:unpin:* notification.
    handleToggleMuteUser() Mutes the author, or unmutes them when they're muted, and publishes an api:user:mute:* or api:user:unmute:* notification.
    handleToggleBanUser() Bans the author with client.moderation.ban(), or unbans them when message.user.banned is set. No notification. Rejects when the request fails.
    handleToggleBlockUser(user) Blocks the user, or unblocks them when they're blocked. Logs a failure instead of rejecting.
    handleToggleReaction(type) Adds the reaction, or removes it when the current user has already reacted with that type. Logs a failure instead of rejecting.

    The handlers that publish a notification catch their own errors and publish a failed notification, so they resolve either way.

  • Reads: messageActionHandlers from useMessageContext(). No store. The handlers read what they act on, such as the mute and block lists, when they run.

  • Re-renders: when Message provides a new context value.

  • Needs: Chat, a Channel or a Thread, and a Message. Outside a Message it returns undefined.

Message builds one set for each message, and the menu and any component that calls this hook share it, so calling it costs nothing. The handlers don't call the handle* props of Channel: the actions from useMessageActions() do. Message actions in your own components explains the difference.

useMessageActions

Returns the default actions of the message being rendered, as the menu shows them.

useMessageActions(): {
  banUser: MessageActionType;
  blockUser: MessageActionType;
  copyMessage: MessageActionType;
  deleteForMeMessage: MessageActionType;
  deleteMessage: MessageActionType;
  editMessage: MessageActionType;
  flagMessage: MessageActionType;
  handleReaction: (reactionType: string) => Promise<void>;
  markUnread: MessageActionType;
  muteUser: MessageActionType;
  pinMessage: MessageActionType;
  quotedReply: MessageActionType;
  retry: MessageActionType;
  threadReply: MessageActionType;
  unpinMessage: MessageActionType;
}
  • Returns: one MessageActionType for each default action: its action, title, icon and type. Each action calls the handle* prop of Channel or Thread first, as the menu does, then the handler from useMessageActionHandlers(). The titles are translated, and the mute, block and ban titles follow the author's current state.
  • Reads: the message, messages and channel contexts, and the author's mute state from client.mutedUsersStore.
  • Re-renders: when the message changes, and when the author is muted or unmuted.
  • Needs: Chat, a Channel or a Thread, and a Message. Throws outside them.

The SDK calls it only while a message's menu is open, because building the actions translates every title and subscribes to the author's mute state. Call it in a custom menu, not in every message row. The fields of MessageActionType are on Actions.

useMessageOperations

Returns the operations that change messages and reactions in the list the component renders in.

useMessageOperations(): MessageOperations
  • Returns:
    • deleteMessage(message, options?): deletes the message. options is { hard: true } or { delete_for_me: true }. A message that failed to send is only removed from the list, because the server never had it.
    • retrySendMessage(message): sends a failed message again.
    • sendReaction(type, messageId) and deleteReaction(type, messageId): add or remove the current user's reaction. A new reaction replaces the user's others when enforceUniqueReaction is set on Channel.
    • removeMessage({ id, parent_id? }): removes the message from the lists and from the offline database, without a request.
    • updateMessage(message): puts a message into the lists without a request: the channel's, unless it's a reply not shown in the channel, and the open thread's when it's a reply.
  • Reads: the Channel, Thread and Chat contexts. No store. Each operation picks its owner when it runs: the thread for a reply loaded in the open thread, otherwise the channel.
  • Re-renders: never by itself. The functions keep their identity.
  • Needs: Chat, and a Channel or a Thread. Throws outside them.

The delete, retry and reaction operations go through messageOperations of the thread or the channel, which shows the change at once and rolls it back if the server refuses. They reject when the server refuses the request. sendReaction and deleteReaction throw before any request when the channel isn't initialized. Message operations explains what happens before the server answers.

useIsMessageReadByOthers

Says whether another member has read a message.

useIsMessageReadByOthers(params: { message?: LocalMessage }): boolean
  • Returns: true when the message is at or before the furthest point any other member has read. The hook compares every message with that one point, so an older message never reports less than a newer one. false without a message.
  • Reads: channel.messageReceiptsTracker.snapshotStore, field lastReadRefByOthers, of the channel of the Channel or Thread.
  • Re-renders: when the answer changes.
  • Needs: a Channel or a Thread. Throws outside both.

MessageStatus uses it, and useIsMessageDeliveredToOthers, for the default read and delivery icons. The tracker is on Message receipts tracker, and the feature on Read state, unread and receipts.

useIsMessageDeliveredToOthers

Says whether a message has reached another member.

useIsMessageDeliveredToOthers(params: { message?: LocalMessage }): boolean
  • Returns: true when the message is at or before the furthest point any other member has received. false without a message.
  • Reads: channel.messageReceiptsTracker.snapshotStore, field lastDeliveredRefByOthers.
  • Re-renders: when the answer changes.
  • Needs: a Channel or a Thread. Throws outside both.

useMessageReadData

Returns the users who have read a message.

useMessageReadData(params: { message?: LocalMessage }): UserResponse[]
  • Returns: every user whose read position is at the message or later, the current user included. [] without a message.
  • Reads: channel.messageReceiptsTracker.snapshotStore, through MessageReceiptsTracker.readersForMessage().
  • Re-renders: whenever the tracker publishes, because each call builds a new list.
  • Needs: a Channel or a Thread. Throws outside both.

Leave the current user out for a "Seen by" label. For a flag only, use useIsMessageReadByOthers, which re-renders less.

useMessageDeliveredData

Returns the users who have received a message.

useMessageDeliveredData(params: { message?: LocalMessage }): UserResponse[]
  • Returns: every user who has received the message, readers included, the current user included. [] without a message.
  • Reads: channel.messageReceiptsTracker.snapshotStore, through MessageReceiptsTracker.deliveredForMessage().
  • Re-renders: whenever the tracker publishes.
  • Needs: a Channel or a Thread. Throws outside both.

useFetchReactions

Loads the reactions of a message, a page at a time, and keeps them current.

useFetchReactions(params: {
  limit?: number;
  message?: LocalMessage;
  reactionType?: string;
  sort?: SortParamRequest[];
}): {
  loading: boolean;
  loadNextPage: () => Promise<void>;
  reactions: ReactionResponse[];
}

limit is 25 by default. With reactionType, only the reactions of that type load.

  • Returns:
    • reactions: the reactions loaded so far.
    • loadNextPage(): loads the next page. It does nothing after the last one.
    • loading: true until the first page loads. It stays true when the request fails.
  • Reads: client.queryReactionsAndHydrate(), into a list the hook keeps for the component. It loads the first page when it mounts, and again when the message, the type or the sort changes. It follows reaction.new, reaction.updated and reaction.deleted for the message, and the reactions the offline database returns while offline.
  • Re-renders: when a page loads, and when a reaction event for the message arrives.
  • Needs: Chat. Throws outside it.

A failed request publishes an api:message:reactions:fetch:failed notification. Define sort at module scope: a new array on each render loads the first page again. The reactions screen of the message menu uses this hook. Reactions shows a list of who reacted built on it.

useMessageDateSeparator

Returns the date to show in a separator above a message.

useMessageDateSeparator(params: {
  hideDateSeparators?: boolean;
  message?: LocalMessage;
  previousMessage?: LocalMessage;
}): Date | undefined
  • Returns: the message's created_at as a Date when it falls on another day than previousMessage, or when there's no previous message. undefined otherwise, and always with hideDateSeparators.
  • Reads: its params only.
  • Re-renders: never by itself.
  • Needs: nothing.

useMessageGroupStyles

Returns where a message sits in its group of consecutive messages from one user.

useMessageGroupStyles(params: {
  message: LocalMessage;
  previousMessage?: LocalMessage;
  nextMessage?: LocalMessage;
  dateSeparatorDate?: Date;
  maxTimeBetweenGroupedMessages?: number;
  noGroupByUser?: boolean;
  getMessageGroupStyle: MessagesContextValue["getMessageGroupStyle"];
}): string[]
  • Returns: the group styles from getMessageGroupStyle, such as ['top'], ['middle'], ['bottom'] or ['single']. Without a getMessageGroupStyle, the SDK's own function. [] with noGroupByUser.
  • Reads: its params only.
  • Re-renders: never by itself.
  • Needs: nothing.

The message lists use these two hooks for each row. They render InlineDateSeparator with the date, and pass the group styles to Message. Use them in a message list of your own.

useTranslatedMessage

Returns a message with its text in the user's language.

useTranslatedMessage(message?: LocalMessage | MessageResponse): LocalMessage | MessageResponse | undefined
  • Returns: a copy of the message whose text is message.i18n.<userLanguage>_text when the message has that translation, and the message's own text otherwise. A deleted message keeps its text. undefined without a message.
  • Reads: userLanguage from TranslationContext.
  • Re-renders: when the user's language changes.
  • Needs: Chat or OverlayProvider. Throws outside both.

getTranslatedMessage(message, userLanguage) does the same outside a component, with the language you pass. The SDK's message text, channel preview and pinned messages show the text the same way. See Message translation.

Best practices

  • Inside a message, take the handlers from useMessageContext() or useMessageActionHandlers(). Call useMessageOperations() only for an operation the handlers don't cover, or outside a message.
  • Call useMessageActions() in a custom menu, not in every row.
  • Check what the user may do with useOwnCapabilitiesContext() before you show an action, so a user never sees a button the server would refuse.
  • Wrap useMessageOperations() calls in try/catch, and report the outcome with useNotificationApi. The handlers that publish a notification already do.
  • Edit a message in the composer with setEditingState(message) from useMessageComposerAPIContext(), or handleEditMessage(). There's no edit operation.

Examples

Mark unread

The handler publishes its own notification, success or failure:

import { Pressable, Text } from "react-native";
import {
  useMessageActionHandlers,
  useMessageContext,
  useOwnCapabilitiesContext,
} from "stream-chat-react-native";

export const MarkUnreadButton = () => {
  const { isMyMessage } = useMessageContext();
  const { handleMarkUnreadMessage } = useMessageActionHandlers();
  const { readEvents } = useOwnCapabilitiesContext();

  if (isMyMessage || !readEvents) return null;

  return (
    <Pressable
      accessibilityRole="button"
      onPress={() => void handleMarkUnreadMessage()}
    >
      <Text>Mark unread</Text>
    </Pressable>
  );
};

Reaction buttons

import { Pressable, Text, View } from "react-native";
import { useMessageContext } from "stream-chat-react-native";

export const QuickReactions = () => {
  const { handleReaction } = useMessageContext();

  if (!handleReaction) return null;

  return (
    <View style={{ flexDirection: "row" }}>
      <Pressable
        accessibilityRole="button"
        onPress={() => void handleReaction("love")}
      >
        <Text>Love</Text>
      </Pressable>
      <Pressable
        accessibilityRole="button"
        onPress={() => void handleReaction("like")}
      >
        <Text>Like</Text>
      </Pressable>
    </View>
  );
};

Use the types of your supportedReactions. The defaults are like, haha, love, wow and sad, and the emoji types of the full picker: see Reactions.

Retry a failed message

import { Pressable, Text } from "react-native";
import type { LocalMessage } from "stream-chat";
import { useMessageOperations } from "stream-chat-react-native";

export const RetryButton = ({ message }: { message: LocalMessage }) => {
  const { retrySendMessage } = useMessageOperations();

  if (message.status !== "failed") return null;

  return (
    <Pressable
      accessibilityRole="button"
      onPress={() => retrySendMessage(message).catch(() => undefined)}
    >
      <Text>Retry</Text>
    </Pressable>
  );
};

A failed retry leaves the message marked failed, so the button stays. The Custom message UI cookbook builds a whole message component on these hooks.