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

Emoji

Users type emoji with the device keyboard in any app. This page covers what the SDK adds on top: :shortcode suggestions in the composer, which are optional and need an emoji search index such as emoji-mart's, and the larger rendering of messages that hold only emoji. Emoji reactions are covered on Reactions.

What users get

The user types : followed by a few letters. After :piz, a list suggests the pizza emoji and other matches, each with its name, under the header Emoji matching "piz". Picking one replaces :piz with the emoji and a space. When they type an emoticon with a colon that the search knows, such as :), it's replaced with its emoji as they type.

A message that holds only emoji renders them large, without the message bubble.

The SDK has no emoji picker in the composer, so users pick emoji from the keyboard. You can add a row of your own, as A row of quick emoji shows.

How it works across the layers

  • State. An emoji is text. Suggestions come from a text composer middleware, like the ones for mentions and commands, with : as its trigger. Picking one writes the emoji into textComposer.
  • UI. AutoCompleteSuggestionList shows the suggestions, and EmojiSuggestionItem renders each one. MessageTextContainer renders an emoji-only message with the larger style.
  • Layout. Not involved.

Suggestions

createTextComposerEmojiMiddleware({ emojiSearchIndex }) builds the middleware. It needs a search index that finds emoji by name: emoji-mart's SearchIndex, or any object with a search(query) method that returns emoji.

  1. On each text change, the middleware looks for a : and the text after it, just before the cursor.
  2. If the word before the cursor is an emoticon of an emoji the index returns, the word is replaced with that emoji.
  3. Otherwise it opens the suggestions with up to seven emoji that the index finds for the text after the colon. An emoji without a glyph, on itself or on its first skin, is left out.
  4. Picking one replaces the :query with the emoji and a space.

The middleware works like a custom trigger. The suggestion list renders emoji rows for the : trigger.

Emoji-only messages

When a message's text is made of emoji and spaces only, and the message has no images, files, link cards or Giphy, its text renders with the onlyEmojiMarkdown style of the theme, 50 points by default, and the bubble has no background or border. With a visible quoted reply, the bubble stays.

Turning it on or off

Suggestions are off by default. Install emoji-mart and its data. They aren't dependencies of the SDK:

Terminal
yarn add emoji-mart @emoji-mart/data

Initialize emoji-mart's search with its data once, and add the middleware in the composer setup function:

import data from "@emoji-mart/data";
import { init, SearchIndex } from "emoji-mart";
import type { StreamChat, TextComposerMiddleware } from "stream-chat";
import { createTextComposerEmojiMiddleware } from "stream-chat-react-native";

init({ data });

export const setUpComposers = (client: StreamChat) =>
  client.config.setSetupFunction("messageComposer", ({ composer }) => {
    // One middleware per composer, so composers don't share suggestions.
    const emojiMiddleware = createTextComposerEmojiMiddleware({
      emojiSearchIndex: SearchIndex,
    }) as TextComposerMiddleware;

    composer.textComposer.middlewareExecutor.insert({
      middleware: [emojiMiddleware],
      position: { before: "stream-io/text-composer/mentions-middleware" },
      unique: true,
    });

    return () =>
      composer.textComposer.middlewareExecutor.remove(emojiMiddleware.id);
  });

The setup function runs for every composer: channel, thread and edit composers, existing and new. There's one setup function per kind of object, so if your app also installs the command middleware, put both in the same function.

The larger rendering of emoji-only messages is always on. Its size is a theme value, set as The size of emoji-only messages shows.

Customizing

Your own search index

Pass any object with search(query) as emojiSearchIndex. It returns emoji, or a promise of them, each with id, name and skins (a list of { native }), and optionally native, emoticons and keywords. The middleware inserts native, or the first skin's. This index offers a few emoji of your own:

import type { Emoji, EmojiSearchIndex } from "stream-chat-react-native";

const teamEmoji: Emoji[] = [
  {
    emoticons: [":)"],
    id: "smile",
    name: "Smile",
    native: "\u{1F604}",
    skins: [],
  },
  { id: "rocket", name: "Rocket", native: "\u{1F680}", skins: [] },
  { id: "tada", name: "Party", native: "\u{1F389}", skins: [] },
];

export const teamEmojiSearchIndex: EmojiSearchIndex = {
  search: (query) =>
    teamEmoji.filter(
      ({ id, name }) =>
        id.includes(query) || name.toLowerCase().includes(query),
    ),
};

createTextComposerEmojiMiddleware also takes options: minChars, the shortest :query that opens suggestions, counting the colon (1 by default), and trigger (: by default). The default suggestion list renders emoji rows only for the : trigger, so another trigger also needs a list of your own. The AutoCompleteSuggestionItem override changes how each suggestion looks, and AutoCompleteSuggestionHeader the header, whose "Emoji matching" isn't a translation key. See the Custom suggestions UI cookbook, and the Emoji suggestions UI cookbook for the whole setup.

A row of quick emoji

MessageComposerTrailingView renders after the input box, and is empty by default. This row inserts an emoji at the cursor, or in place of the selected text, and keeps the focus in the input:

import { Pressable, StyleSheet, Text, View } from "react-native";
import {
  useMessageComposer,
  useMessageInputContext,
} from "stream-chat-react-native";
import type { ComponentOverrides } from "stream-chat-react-native";

const quickEmoji = ["\u{1F44D}", "\u{1F389}", "\u{1F602}", "\u{1F64F}"];

const QuickEmojiRow = () => {
  const { textComposer } = useMessageComposer();
  const { inputBoxRef } = useMessageInputContext();

  const insert = (emoji: string) => {
    textComposer.insertText({ text: emoji });
    inputBoxRef.current?.focus();
  };

  return (
    <View style={styles.row}>
      {quickEmoji.map((emoji) => (
        <Pressable
          accessibilityRole="button"
          key={emoji}
          onPress={() => insert(emoji)}
        >
          <Text style={styles.emoji}>{emoji}</Text>
        </Pressable>
      ))}
    </View>
  );
};

// Pass to `WithComponents`, declared once at module scope.
export const overrides: ComponentOverrides = {
  MessageComposerTrailingView: QuickEmojiRow,
};

const styles = StyleSheet.create({
  emoji: { fontSize: 20, padding: 4 },
  row: { alignSelf: "flex-end", flexDirection: "row", paddingBottom: 8 },
});

The size of emoji-only messages

The style is the theme's messageItemView.content.textContainer.onlyEmojiMarkdown, a Markdown style. Pass a smaller one in the style of Chat:

import type { PropsWithChildren } from "react";
import type { StreamChat } from "stream-chat";
import { Chat } from "stream-chat-react-native";
import type { DeepPartial, Theme } from "stream-chat-react-native";

// Declared once at module scope.
const style: DeepPartial<Theme> = {
  messageItemView: {
    content: {
      textContainer: { onlyEmojiMarkdown: { text: { fontSize: 32 } } },
    },
  },
};

export const ChatWithSmallerEmoji = ({
  children,
  client,
}: PropsWithChildren<{ client: StreamChat }>) => (
  <Chat client={client} style={style}>
    {children}
  </Chat>
);

The bubble stays without a background at any size. Theming explains the theme.

Reference