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 intotextComposer. - UI.
AutoCompleteSuggestionListshows the suggestions, andEmojiSuggestionItemrenders each one.MessageTextContainerrenders 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.
- On each text change, the middleware looks for a
:and the text after it, just before the cursor. - If the word before the cursor is an emoticon of an emoji the index returns, the word is replaced with that emoji.
- 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.
- Picking one replaces the
:querywith 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:
yarn add emoji-mart @emoji-mart/datanpx expo install emoji-mart @emoji-mart/dataInitialize 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
- Suggestions: how text middleware opens suggestions.
- Composer UI components:
AutoCompleteSuggestionListand its items. - Composer middleware: inserting middleware and the setup function.
- Theme object:
messageItemView.content.textContainer.