# 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:

```tsx
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 MessageComposer components */}
</Channel>;
```

### Reaction List Position and Style

Reactions can render at the `top` or `bottom` using `reactionListPosition` on `Channel`.
You can also control the visual style with `reactionListType`.

- `reactionListPosition`: `top` | `bottom`
- `reactionListType`: `clustered` | `segmented`

For bottom-aligned reactions, `segmented` usually produces a more natural layout.

```tsx
<Channel reactionListPosition="bottom" reactionListType="segmented">
  {/* Underlying MessageList and MessageComposer components */}
</Channel>
```

<table>
  <tr>
  <td align='center' width='33%'>

![Top Reactions](@chat-sdk/react-native/v9-latest/_assets/guides/customize-message-reactions/reaction-list-top.png)

  </td>
  <td align='center' width='33%'>

![Bottom Reactions](@chat-sdk/react-native/v9-latest/_assets/guides/customize-message-reactions/reaction-list-bottom.png)

  </td>
  </tr>
  <tr></tr>
  <tr>
  <td align='center'>Top Reactions</td>
  <td align='center'>Bottom Reactions</td>
  </tr>
</table>

### 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.

```tsx
<Channel enforceUniqueReaction>
  {/* Underlying MessageList and MessageComposer components */}
</Channel>
```

### Customizing message Reaction List components

When a message has reactions, you can customize:

- `ReactionListTop`
- `ReactionListBottom`

By passing custom UI via `WithComponents`:

![Bottom ReactionList customization](@chat-sdk/react-native/v9-latest/_assets/guides/customize-message-reactions/reaction-list-bottom-customization.png)

```tsx
import { StyleSheet, View } from "react-native";
import {
  ReactionListItemWrapper,
  useMessageContext,
  WithComponents,
} from "stream-chat-react-native";

const CustomReactionListBottomComponent = () => {
  const {
    handleReaction,
    onPress,
    onLongPress,
    onPressIn,
    preventPress,
    reactions,
    showReactionsOverlay,
  } = useMessageContext();

  return (
    <View style={styles.container}>
      {reactions?.map((reaction) => {
        const { type, Icon } = reaction;
        return (
          <ReactionListItemWrapper
            disabled={preventPress}
            key={type}
            onLongPress={(event) => {
              onLongPress?.({
                defaultHandler: () => {
                  showReactionsOverlay(type);
                },
                emitter: "reactionList",
                event,
              });
            }}
            onPress={(event) => {
              onPress?.({
                defaultHandler: () => {
                  if (handleReaction) {
                    void handleReaction(type);
                  }
                },
                emitter: "reactionList",
                event,
              });
            }}
            selected={reaction.own}
          >
            {Icon ? <Icon /> : null}
          </ReactionListItemWrapper>
        );
      })}
    </View>
  );
};

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

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

### Customizing Reaction Picker component

Customize the reaction picker via the `MessageReactionPicker` override in `WithComponents`.

If you fully replace it, you own the reaction selection and overlay dismiss behavior. As a note, make sure to use `scheduleActionOnClose` for any and all actions that affect the underlying `MessageList`'s layout as it will make sure they're executed after the closing animations of the context menu are finished.

![Message Reaction Picker customization](@chat-sdk/react-native/v9-latest/_assets/guides/customize-message-reactions/message-reaction-picker-customization.png)

```tsx
import { useMemo, useCallback } from "react";
import { FlatList, Pressable, StyleSheet, View } from "react-native";
import {
  MessageReactionPickerProps,
  ReactionData,
  scheduleActionOnClose,
  useMessagesContext,
  useMessageContext,
  WithComponents,
} from "stream-chat-react-native";

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

  const renderItem = useCallback(
    ({ item }: { item: ReactionData }) => {
      return (
        <Pressable
          onPress={() => {
            dismissOverlay();
            if (handleReaction) {
              scheduleActionOnClose(() => handleReaction(item.type));
            }
          }}
          style={styles.reactionButton}
        >
          {item.Icon ? <item.Icon /> : null}
        </Pressable>
      );
    },
    [dismissOverlay, handleReaction],
  );

  const reactionData = useMemo(
    () => supportedReactions?.slice(0, 30) ?? [],
    [supportedReactions],
  );

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

const ChannelScreen = () => {
  return (
    <WithComponents
      overrides={{ MessageReactionPicker: CustomMessageReactionPicker }}
    >
      <Channel channel={channel}>
        {/* Underlying MessageList and MessageComposer components */}
      </Channel>
    </WithComponents>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 8,
    marginBottom: 8,
    backgroundColor: "grey",
    borderRadius: 12,
  },
  row: {
    justifyContent: "space-evenly",
  },
  reactionButton: {
    padding: 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](@chat-sdk/react-native/v9-latest/_assets/guides/customize-message-reactions/message-user-reactions-customization.png)

```tsx
import { useMemo, useCallback } from "react";
import { FlatList, StyleSheet, Text, View } from "react-native";
import { ReactionSortBase } from "stream-chat";
import {
  MessageUserReactionsAvatar,
  Reaction,
  useFetchReactions,
  useMessageContext,
  useMessagesContext,
} from "stream-chat-react-native";

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

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

  const reactions = useMemo(
    () =>
      fetchedReactions.map((reaction) => ({
        id: `${reaction.user?.id}-${reaction.type}-${String(reaction.created_at ?? "unknown")}`,
        image: reaction.user?.image,
        name: reaction.user?.name,
        type: reaction.type,
      })) as Reaction[],
    [fetchedReactions],
  );

  const renderItem = useCallback(
    ({ 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>
      );
    },
    [supportedReactions],
  );

  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` components by passing them as overrides to `WithComponents`.


---

This page was last updated at 2026-07-10T16:05:12.369Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/guides/customize-message-reactions/](https://getstream.io/chat/docs/sdk/react-native/guides/customize-message-reactions/).