Overview

The core of Stream Chat React Native is its flexibility. Many apps only need the theme. For deeper changes or new behavior, build a custom component.

Best Practices

  • Start with theming; only create custom components when behavior/layout must change.
  • Use WithComponents to override default UI components at any level of the tree.
  • Return null to remove UI features cleanly.
  • Use the right context for your component's scope; avoid message-level re-renders.
  • Keep custom components lightweight, especially inside MessageList.

When to use a custom component

Build a custom component when you need to insert custom UI, significantly change layout, or modify behavior. With few exceptions, what you see is what you get.

Exceptions

  • Padding, fonts, colors, borders, etc. can all be altered from the theme as styles are passed to most components and sub-components.
  • Messages can be aligned to one side using the forceAlignMessages prop on the Channel component.
  • Message content can be reordered using the messageContentOrder prop on Channel; but you may want to adjust the theming to account for changes to border alignments if you alter this order.

Changes beyond these tweaks require custom components. To remove a UI feature, replace it with a component that returns null.

How to use a custom component

Custom components are provided via the WithComponents wrapper. It accepts an overrides prop — an object mapping component names to your replacements. Wrap WithComponents around the part of the tree where the overrides should apply (typically around Channel, ChannelList, or even at the top level around Chat).

Overrides are inherited: inner WithComponents wrappers merge over outer ones, so you can set app-wide defaults and override specific components deeper in the tree.

To remove the fixed DateHeader, pass a component that returns null:

import {
  WithComponents,
  Channel,
  MessageList,
  MessageInput,
} from "stream-chat-react-native";

<WithComponents overrides={{ DateHeader: () => null }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </Channel>
</WithComponents>;

Your custom component replaces the default everywhere it is used within that subtree. Most components read from context; some also receive props. DateHeader receives a dateString prop.

import {
  WithComponents,
  Channel,
  MessageList,
  MessageInput,
} from "stream-chat-react-native";

const MyNewComponent = ({ dateString }) => (
  <Text
    style={{ alignSelf: "center", backgroundColor: "red" }}
  >{`Hello World: ${dateString}`}</Text>
);

<WithComponents overrides={{ DateHeader: MyNewComponent }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </Channel>
</WithComponents>;

DateHeader

NoDateHeader

NewDateHeader

Using contexts

Custom components typically read from SDK contexts. Use the hooks within their provider scope. Some contexts, like MessageContext, are only available inside a Message, so you can't use them in DateHeader.

You can also access the resolved set of components via the useComponentsContext() hook. This returns all components with defaults filled in, merged with any overrides from WithComponents.

Example: replace DateHeader with a component that reads PaginatedMessageListContext and displays the last sender.

import {
  WithComponents,
  Channel,
  MessageList,
  MessageInput,
  usePaginatedMessageListContext,
} from "stream-chat-react-native";

const MySenderComponent = () => {
  const { messages } = usePaginatedMessageListContext();
  const latestMessageSender = messages[messages.length - 1]?.user?.name;

  return (
    <Text
      style={{ alignSelf: "center", backgroundColor: "red" }}
    >{`Last Sender: ${latestMessageSender}`}</Text>
  );
};

<WithComponents overrides={{ DateHeader: MySenderComponent }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </Channel>
</WithComponents>;

NameDateHeader

This lets you tailor both UI and the information you show.

Custom components within a Message should draw from mostly static contexts, except their own MessageContext. ThemeContext, MessagesContext, and TranslationContext are safe in most cases.

Memoizing values from contexts above the FlatList inside a row does not work well when those contexts update. Reading PaginatedMessageListContext inside each Message will cause the whole list to re-render.

Common customization patterns

Here are some frequently requested customization examples:

Custom message bubble

Replace the default message appearance with a custom design:

import { View, Text, StyleSheet } from "react-native";
import {
  WithComponents,
  Channel,
  MessageList,
  MessageInput,
  useMessageContext,
} from "stream-chat-react-native";

const CustomMessageContent = () => {
  const { message, isMyMessage } = useMessageContext();

  return (
    <View
      style={[
        styles.messageContainer,
        isMyMessage ? styles.myMessage : styles.theirMessage,
      ]}
    >
      {!isMyMessage && (
        <Text style={styles.username}>{message.user?.name}</Text>
      )}
      <Text style={styles.messageText}>{message.text}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  messageContainer: {
    padding: 12,
    borderRadius: 16,
    maxWidth: "80%",
    marginVertical: 4,
  },
  myMessage: {
    backgroundColor: "#007AFF",
    alignSelf: "flex-end",
  },
  theirMessage: {
    backgroundColor: "#E5E5EA",
    alignSelf: "flex-start",
  },
  username: {
    fontSize: 12,
    color: "#666",
    marginBottom: 4,
  },
  messageText: {
    fontSize: 16,
  },
});

// Usage
<WithComponents overrides={{ MessageContent: CustomMessageContent }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </Channel>
</WithComponents>;

Custom send button

Create a send button with custom styling or behavior:

import { TouchableOpacity, StyleSheet } from "react-native";
import {
  WithComponents,
  Channel,
  MessageList,
  MessageInput,
  useMessageInputContext,
  SendIcon,
} from "stream-chat-react-native";

const CustomSendButton = () => {
  const { sendMessage } = useMessageInputContext();

  return (
    <TouchableOpacity style={styles.sendButton} onPress={sendMessage}>
      <SendIcon />
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  sendButton: {
    backgroundColor: "#007AFF",
    borderRadius: 20,
    padding: 8,
    marginLeft: 8,
  },
});

// Usage
<WithComponents overrides={{ SendButton: CustomSendButton }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </Channel>
</WithComponents>;

Remove a UI element

To completely remove a UI element, pass a component that returns null in the overrides:

import { WithComponents, Channel, MessageList, MessageInput } from "stream-chat-react-native";

// Remove the typing indicator and date header
<WithComponents overrides={{ TypingIndicator: () => null, DateHeader: () => null }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </Channel>
</WithComponents>

// Remove message avatars
<WithComponents overrides={{ MessageAuthor: () => null }}>
  <Channel channel={channel}>
    <MessageList />
    <MessageInput />
  </Channel>
</WithComponents>