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

Mentions

What users get

Typing @ in the composer suggests people and groups to mention. The sent message highlights each mention, and the server notifies the people it names.

  • Suggestions. @ opens a list above the composer with channel members, @channel, @here, roles and user groups, narrowed as the user types. When the channel's members are searched locally, common accents are ignored, so "rene" finds "Rene" spelled with an accent.
  • Inserting. Tapping a suggestion inserts it into the text, for example @Ben. Deleting that text removes the mention.
  • Highlighting. Mentions in sent messages are colored by kind, and can be pressed.

There are five kinds of mention:

Kind Typed as Notifies Message field
user @<name> one user mentioned_users
channel @channel everyone in the channel mentioned_channel
here @here members who are online mentioned_here
role @<role> users with that role mentioned_roles
user_group @<group> members of a user group mentioned_group_ids when sending, mentioned_groups when read

How it works across the layers

State: the text composer

The user types "Thanks @be" in "Design":

  1. The text composer runs its middleware on every change. The mentions middleware finds the @be before the cursor and asks MentionsSearchSource for suggestions.
  2. The search source collects them:
    • Users. When the channel has fewer than 100 members loaded, it searches its loaded members and watchers locally, by name and id. Otherwise it queries the channel's members by name. With mentionAllAppUsers, it queries all users of the app instead.
    • @channel and @here, when "be" starts their name.
    • Roles through client.searchRoles(), and user groups through client.searchUserGroups(), which is scoped to the channel's team. Both need at least one character after @.
  3. The suggestions land in the text composer's state as suggestions, and the UI lists them.
  4. The user picks Ben. The middleware replaces @be with @Ben and adds Ben to the text composer's mentions.
  5. When they send, the composer writes each mention to the message field of its kind, from the table above.

Each kind other than user is offered only when the user has its capability in the channel (see Turning it on or off). The capabilities are checked on every search, so the suggestions follow when they change.

The middleware and the search source are explained on Suggestions.

UI: suggestions and highlighting

AutoCompleteSuggestionList renders the list while the text composer has suggestions. MessageList renders it, above the composer:

  • AutoCompleteSuggestionHeader titles the list.
  • Each row is an AutoCompleteSuggestionItem, which calls TextComposer.handleSelect() when it's pressed. For @, the row renders MentionSuggestionItem, which picks MentionUserItem, MentionBroadcastItem (@channel and @here), MentionRoleItem or MentionUserGroupItem by the suggestion's mentionType.

In the message list, renderText finds each mention in the message's text by matching the names in its mention fields, case-insensitively. It renders the mention in its own color: one for users, one for @channel and @here, one for roles and one for user groups. Pressing a mention calls the message's press handler with the textMention emitter. By default that dismisses the keyboard, or, on a message that failed to send or bounced, opens the menu or the bounce alert. See Reacting to presses.

Turning it on or off

User mentions are always available. The other kinds need a capability in the channel:

Kind Capability Permission in the dashboard
channel notify-channel NotifyChannel
here notify-here NotifyHere
role notify-role NotifyRole
user_group notify-group NotifyGroup

Grant them in the Stream Dashboard under Chat Messaging, Roles & Permissions: pick the role (for example channel_member) and the channel type (for example messaging), then turn on the permissions. Until the user has a capability, that kind doesn't appear in the suggestions.

Two kinds also need data on the server:

  • Roles are the roles defined on the same Roles & Permissions page.
  • User groups are named sets of users, mentioned as one with @<group name>. They don't depend on channel membership: a group can be mentioned in any channel where group mentions are allowed. Create and manage them with a server-side SDK; see the User Groups API reference.

Customizing

Give the mentions middleware a search source with other options, in a composer setup function. This one suggests every user of the app, not only the channel's members:

import type { StreamChat, TextComposerMiddleware } from "stream-chat";
import { createMentionsMiddleware, MentionsSearchSource } from "stream-chat";

export const mentionAllAppUsers = (client: StreamChat) =>
  client.config.setSetupFunction("messageComposer", ({ composer }) => {
    const { channel, textComposer } = composer;
    const searchSource = new MentionsSearchSource(channel, {
      mentionAllAppUsers: true,
    });

    // Same id as the default mentions middleware, so it takes its place.
    textComposer.middlewareExecutor.replace([
      createMentionsMiddleware(channel, {
        searchSource,
      }) as TextComposerMiddleware,
    ]);

    return () => {
      textComposer.middlewareExecutor.replace([
        createMentionsMiddleware(channel) as TextComposerMiddleware,
      ]);
    };
  });

Other options of MentionsSearchSource:

  • transliterate turns the typed text and the names into Latin script before they're compared, (text: string) => string, for example the default export of @sindresorhus/transliterate. It applies where the search source compares names itself: the local member search and @channel and @here. Queries to the server aren't transliterated.
  • memberFilters replaces the default member query. userFilters adds conditions to the user query that mentionAllAppUsers makes.
  • memberSort and userSort replace the default order.

There is one setup function per kind, so this one replaces any other messageComposer setup function, such as one that calls setupCommandUIMiddlewares. Do all composer setup in one function. See Suggestions.

Replacing a suggestion row

MentionSuggestionItem is an override key. It receives the suggestion as props. This one replaces the user rows and keeps the default for the other kinds:

import { Text, View } from "react-native";
import type { MentionSuggestion } from "stream-chat";
import {
  MentionSuggestionItem,
  WithComponents,
} from "stream-chat-react-native";

const MentionRow = (item: MentionSuggestion) => {
  if (item.mentionType !== "user") return <MentionSuggestionItem {...item} />;

  return (
    <View style={{ paddingHorizontal: 16, paddingVertical: 8 }}>
      <Text>@{item.name ?? item.id}</Text>
    </View>
  );
};

export const App = () => (
  <WithComponents overrides={{ MentionSuggestionItem: MentionRow }}>
    {/* the rest of the app */}
  </WithComponents>
);

AutoCompleteSuggestionList, AutoCompleteSuggestionHeader and AutoCompleteSuggestionItem are override keys too, and replace the list for every trigger. The Custom suggestions UI cookbook builds complete rows.

Styling mentions

A mention's color comes from the semantic color tokens chatTextMentionUser, chatTextMentionBroadcast, chatTextMentionRole and chatTextMentionGroup. The rest of its style, such as the font weight, comes from mentions in messageItemView.content.markdown of the theme. See Theming.

Reacting to presses

onPressMessage and onLongPressMessage on Channel and Thread receive a press on a mention with the textMention emitter. additionalInfo.mentionedEntity is the mention, with its mentionType, and additionalInfo.user is the user for a user mention:

import type { Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
} from "stream-chat-react-native";

export const ChannelWithMentionPresses = ({
  channel,
  openProfile,
}: {
  channel: StreamChannel;
  openProfile: (userId: string) => void;
}) => (
  <Channel
    channel={channel}
    onPressMessage={({ additionalInfo, defaultHandler, emitter }) => {
      if (emitter === "textMention") {
        if (additionalInfo?.user) openProfile(additionalInfo.user.id);
        return;
      }
      defaultHandler?.();
    }}
  >
    <MessageList />
    <MessageComposer />
  </Channel>
);

Reference