import {
WithComponents,
Channel,
MessageList,
MessageInput,
} from "stream-chat-react-native";
<WithComponents overrides={{ DateHeader: () => null }}>
<Channel channel={channel}>
<MessageList />
<MessageInput />
</Channel>
</WithComponents>;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
WithComponentsto override default UI components at any level of the tree. - Return
nullto 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
forceAlignMessagesprop on theChannelcomponent. - Message content can be reordered using the
messageContentOrderprop onChannel; 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:
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>;


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>;
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>