Customize Message Reactions

Message reactions are a key part of chat apps. The SDK includes built-in support.

Best Practices

  • Keep reaction sets small and familiar to reduce UI clutter.
  • Use supportedReactions to keep server and client reaction types aligned.
  • Choose a single reactionListPosition to avoid shifting layouts.
  • Respect enforceUniqueReaction to simplify downstream analytics.
  • Reuse useMessageContext handlers to keep overlay and press behavior consistent.

Supported Reaction types

You can customize the supported types of reactions for your application using the supportedReactions prop of the Channel component.

It expects an array of objects with Icon and type. Use ReactionData for type safety.

Example:

import { Text } from "react-native";
import { ReactionData } from "stream-chat-react-native";

const supportedReactions: ReactionData[] = [
  { type: "happy", Icon: () => <Text>😄</Text> },
  { type: "sad", Icon: () => <Text>😦</Text> },
  { type: "love", Icon: () => <Text>❤️</Text> },
  { type: "angry", Icon: () => <Text>😠</Text> },
  { type: "haha", Icon: () => <Text>😆</Text> },
];

<Channel supportedReactions={supportedReactions}>
  {/* Underlying MessageList and MessageInput components */}
</Channel>;

Reaction List Position

Reactions can render at the top or bottom using reactionListPosition on Channel.

<Channel reactionListPosition="bottom">
  {/* Underlying MessageList and MessageInput components */}
</Channel>

Top Reactions

Bottom Reactions

Top ReactionsBottom Reactions

Enforce unique reactions

You can enforce a single reaction per user per message.

To do this, you can enable the enforceUniqueReaction prop in the Channel component.

<Channel enforceUniqueReaction>
  {/* Underlying MessageList and MessageInput components */}
</Channel>

Customizing message Reaction List components

When a message has reactions, you can customize:

  • ReactionListTop
  • ReactionListBottom

Pass custom UI via Channel props:

Bottom ReactionList customization

import { Pressable, StyleSheet } from "react-native";
import { Channel, useMessageContext } from "stream-chat-react-native";

const CustomReactionListBottomComponent = () => {
  const {
    handleReaction,
    onPress,
    onLongPress,
    reactions,
    showMessageOverlay,
  } = useMessageContext();

  return (
    <View style={styles.container}>
      {reactions?.map((reaction) => {
        const { type, Icon } = reaction;
        return (
          <Pressable
            onLongPress={(event) => {
              if (onLongPress) {
                onLongPress({
                  defaultHandler: () => {
                    if (handleReaction) {
                      showMessageOverlay(true);
                    }
                  },
                  emitter: "reactionList",
                  event,
                });
              }
            }}
            onPress={(event) => {
              onPress({
                defaultHandler: () => {
                  if (handleReaction) {
                    handleReaction(reaction.type);
                  }
                },
                emitter: "reactionList",
                event,
              });
            }}
            key={type}
          >
            {Icon ? <Icon /> : null}
          </Pressable>
        );
      })}
    </View>
  );
};

const ChannelScreen = () => {
  return (
    <Channel
      reactionListPosition="bottom"
      ReactionListBottom={CustomReactionListBottomComponent}
    >
      {/* Underlying MessageList and MessageInput components */}
    </Channel>
  );
};

const styles = StyleSheet.create({
  container: {
    borderRadius: 4,
    flexDirection: "row",
    marginVertical: 2,
  },
});

Customizing Reaction Picker component

Customize the reaction picker via MessageReactionPicker on Channel.

Message Reaction Picker customization

import { FlatList, Pressable, StyleSheet } from "react-native";
import {
  Channel,
  useMessageContext,
  useMessagesContext,
} from "stream-chat-react-native";

const CustomMessageReactionPicker = () => {
  const { supportedReactions } = useMessagesContext();
  const { dismissOverlay, handleReaction } = useMessageContext();

  const renderItem = ({ item }: { item: ReactionData }) => {
    return (
      <Pressable
        key={item.type}
        onPress={() => {
          if (handleReaction) {
            handleReaction(item.type);
          }
          dismissOverlay();
        }}
      >
        {item.Icon ? <item.Icon /> : null}
      </Pressable>
    );
  };

  return (
    <FlatList
      columnWrapperStyle={styles.container}
      data={supportedReactions}
      keyExtractor={(item) => item.type}
      numColumns={6}
      renderItem={renderItem}
    />
  );
};

const ChannelScreen = () => {
  return (
    <Channel
      reactionListPosition="bottom"
      MessageReactionPicker={CustomMessageReactionPicker}
    >
      {/* Underlying MessageList and MessageInput components */}
    </Channel>
  );
};

const styles = StyleSheet.create({
  container: {
    justifyContent: "space-evenly",
    marginVertical: 8,
  },
});

Customizing Message Reactions by user list component

We display the list of the reactions reacted by users in the channel in the MessageUserReactions component and each of the item in the list using the MessageUserReactionsItem and the avatar using the MessageUserReactionsAvatar component.

Message User Reactions customization

import { useMemo } from "react";
import { Image, FlatList, StyleSheet, Text, View } from "react-native";
import { ReactionSortBase } from "stream-chat";
import {
  Channel,
  Reaction,
  useFetchReactions,
  useMessageContext,
} from "stream-chat-react-native";

const CustomMessageUserReactions = () => {
  const { message } = useMessageContext();
  const reactionSort: ReactionSortBase = {
    created_at: -1,
  };

  const { reactions: fetchedReactions } = useFetchReactions({
    message,
    sort: reactionSort,
  });

  const reactions = useMemo(
    () =>
      fetchedReactions.map((reaction) => ({
        id: reaction.user?.id,
        image: reaction.user?.image,
        name: reaction.user?.name,
        type: reaction.type,
      })) as Reaction[],
    [fetchedReactions],
  );

  const renderItem = ({ item }: { item: Reaction }) => {
    const Icon = supportedReactions?.find(
      (supportedReaction) => supportedReaction.type === item.type,
    )?.Icon;
    return (
      <View style={styles.container}>
        <MessageUserReactionsAvatar reaction={item} />
        <Text style={styles.text}>{item.name}</Text>
        <View style={styles.icon}>{Icon && <Icon />}</View>
      </View>
    );
  };

  return (
    <FlatList
      data={reactions}
      keyExtractor={(item) => item.id}
      renderItem={renderItem}
    />
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    flexWrap: "wrap",
    marginHorizontal: 16,
    alignItems: "center",
    marginVertical: 8,
  },
  icon: {
    position: "absolute",
    right: 0,
  },
  text: {
    marginHorizontal: 16,
  },
});

Similarly, you can customize the MessageUserReactionsItem and MessageUserReactionsAvatar component.