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

renderText function

renderText(text, mentionedUsers, options) turns a message's text into React elements. The default renders Markdown with react-markdown, turns plain URLs and e-mail addresses into links, and marks mentions and emoji. renderText shows where it fits in a message.

By default it runs these plugins:

  • remark, on the Markdown: htmlToTextPlugin, keepLineBreaksPlugin, remark-gfm (with single tildes left as text), plusPlusToEmphasis and imageToLink;
  • rehype, on the HTML tree: emojiMarkdownPlugin, and the mentions plugin when the message has mentions.

Only the tags in defaultAllowedTagNames render. Raw HTML in the text shows as text.

Best practices

  • Wrap the default renderText rather than replacing it, and pass on the options you receive: they carry the message's mentions.
  • Declare your function outside the component. The default message UI doesn't re-render when only renderText changes.
  • Keep htmlToTextPlugin when the text comes from users, so the HTML they type shows as text. Without it, that HTML is dropped from the message.

Overriding defaults

Pass your function as renderText to MessageList or VirtualizedMessageList, and it renders the text of every message in the list. MessageText, QuotedMessage and StreamedMessageText take a renderText prop too, for a custom message UI.

type RenderTextFunction = (
  text?: string,
  /** Deprecated: use `options.messageMentionEntities`. */
  mentionedUsers?: UserResponse[],
  options?: RenderTextOptions,
) => ReactNode;
Option Type Description
allowedTagNames string[] The tags that render. Defaults to defaultAllowedTagNames. Other tags are dropped and their content kept.
customMarkDownRenderers Options['components'] of react-markdown, plus emoji and mention Components that render a tag, such as strong or a. The SDK renders a, emoji and mention itself.
getRehypePlugins RenderTextPluginConfigurator Receives the default rehype plugins and returns the list to use.
getRemarkPlugins RenderTextPluginConfigurator Receives the default remark plugins and returns the list to use.
messageMentionEntities RenderTextMentionEntity[] The mentions to mark: users, @channel, @here, roles and user groups. MessageText fills it from the message.

RenderTextPluginConfigurator is (defaultPlugins: PluggableList) => PluggableList.

Custom renderText function

A function of your own replaces all of it. This one shows the text as it is, with no Markdown, links, mentions or emoji marks:

import type { RenderTextFunction } from "stream-chat-react";
import { MessageList } from "stream-chat-react";

const renderPlainText: RenderTextFunction = (text) => <span>{text}</span>;

export const PlainTextMessageList = () => (
  <MessageList renderText={renderPlainText} />
);

Custom element rendering

To keep the default output and change how one kind of element renders, pass components in customMarkDownRenderers. mention and emoji are elements the SDK's rehype plugins create. A mention component receives the mention in node.mentionedEntity; for a user mention, node.mentionedUser holds the same user and is deprecated.

This list renders bold text with <b> and links user mentions to a profile page:

import type { PropsWithChildren } from "react";
import type { MentionProps, RenderTextFunction } from "stream-chat-react";
import { MessageList, renderText } from "stream-chat-react";

const Bold = ({ children }: PropsWithChildren) => (
  <b className="message-bold">{children}</b>
);

const ProfileMention = ({ children, node }: MentionProps) => {
  const { mentionedEntity } = node;

  if (mentionedEntity.mentionType !== "user") {
    return (
      <span data-mention-type={mentionedEntity.mentionType}>{children}</span>
    );
  }

  return <a href={`/users/${mentionedEntity.id}`}>{children}</a>;
};

const renderTextWithProfiles: RenderTextFunction = (
  text,
  mentionedUsers,
  options,
) =>
  renderText(text, mentionedUsers, {
    ...options,
    customMarkDownRenderers: {
      ...options?.customMarkDownRenderers,
      mention: ProfileMention,
      strong: Bold,
    },
  });

export const MessagesWithProfiles = () => (
  <MessageList renderText={renderTextWithProfiles} />
);

Styling mentions per type

To style each kind of mention differently, keep the default mention component and use CSS. It renders a <span> with the mention's type and id:

<span
  class="str-chat__message-mention"
  data-mention-type="user | channel | here | role | user_group"
  data-mention-id="<entity id>"
  data-user-id="<user id, only for user mentions>"
  >@Support Team</span
>
.str-chat__message-mention[data-mention-type="channel"],
.str-chat__message-mention[data-mention-type="here"] {
  color: #e62e2e;
}
.str-chat__message-mention[data-mention-type="role"] {
  color: #9c27b0;
}
.str-chat__message-mention[data-mention-type="user_group"] {
  color: #0c8d3a;
}

Mentions explains where each kind of mention comes from.

Custom remark and rehype plugins

getRehypePlugins and getRemarkPlugins receive the default plugins and return the list to use, so you can add to it, reorder it or replace it. A plugin that creates a tag the defaults don't allow needs the tag in allowedTagNames too.

This one wraps every "urgent" in a <mark>:

import type { Nodes } from "hast";
import type { ReplaceFunction } from "hast-util-find-and-replace";
import { findAndReplace } from "hast-util-find-and-replace";
import type {
  RenderTextFunction,
  RenderTextPluginConfigurator,
} from "stream-chat-react";
import {
  defaultAllowedTagNames,
  MessageList,
  renderText,
} from "stream-chat-react";
import { u } from "unist-builder";

const wrapInMark: ReplaceFunction = (match) =>
  u("element", { properties: {}, tagName: "mark" }, [u("text", match)]);

const markUrgent = () => (tree: Nodes) =>
  findAndReplace(tree, [/\burgent\b/gi, wrapInMark]);

const getRehypePlugins: RenderTextPluginConfigurator = (plugins) => [
  ...plugins,
  markUrgent,
];

const renderTextWithMarks: RenderTextFunction = (
  text,
  mentionedUsers,
  options,
) =>
  renderText(text, mentionedUsers, {
    ...options,
    allowedTagNames: [...defaultAllowedTagNames, "mark"],
    getRehypePlugins,
  });

export const MessagesWithMarks = () => (
  <MessageList renderText={renderTextWithMarks} />
);

The example uses hast-util-find-and-replace and unist-builder, as the SDK's own plugins do. Install them in your app to use them. The SDK uses hast-util-find-and-replace 5, and the findAndReplace(tree, [find, replace]) call above is that version's.

Existing remark and rehype plugins

stream-chat-react exports the plugins it uses, and a few more:

Plugin Kind Default What it does
htmlToTextPlugin remark yes Shows HTML in the Markdown as text, so none of it renders.
keepLineBreaksPlugin remark yes Turns each blank line beyond the first between two blocks into a <br>.
plusPlusToEmphasis remark yes Renders ++text++ as <ins>text</ins>, except in code and links.
imageToLink remark yes Renders a Markdown image as a link. Pass [imageToLink, { getTextLabelFrom }] with 'url' (the default), 'alt' or 'title' to pick the link text.
remarkIgnoreMarkdown remark no Shows the whole source as one paragraph of plain text. Plain URLs then show as Markdown links, because renderText rewrites them first.
emojiMarkdownPlugin rehype yes Wraps each emoji in an emoji element.
mentionsMarkdownPlugin(mentionEntities) rehype yes Wraps each mention's display text in a mention element that carries the mention. The default adds it only when the message has mentions.

To pick the plugins yourself, return your own list. This one keeps only keepLineBreaksPlugin and no rehype plugin, so GitHub Markdown, the other remark defaults, mentions and emoji are all off:

import type { RenderTextFunction } from "stream-chat-react";
import {
  keepLineBreaksPlugin,
  MessageList,
  renderText,
} from "stream-chat-react";

const renderTextWithLineBreaksOnly: RenderTextFunction = (
  text,
  mentionedUsers,
  options,
) =>
  renderText(text, mentionedUsers, {
    ...options,
    getRehypePlugins: () => [],
    getRemarkPlugins: () => [keepLineBreaksPlugin],
  });

export const MessagesWithLineBreaksOnly = () => (
  <MessageList renderText={renderTextWithLineBreaksOnly} />
);

Mentions in your own text

MessageText passes the message's mentions in options.messageMentionEntities. When you call renderText for other text, pass the mentions to mark yourself. The second argument, mentionedUsers, covers user mentions only and is deprecated:

import type { RenderTextFunction } from "stream-chat-react";
import { renderText } from "stream-chat-react";

export const renderAnnouncement: RenderTextFunction = (
  text,
  mentionedUsers,
  options,
) =>
  renderText(text, mentionedUsers, {
    ...options,
    messageMentionEntities: [
      ...(options?.messageMentionEntities ?? []),
      { id: "channel", mentionType: "channel", name: "channel" },
      { id: "admin", mentionType: "role", name: "admin" },
    ],
  });