cometchat-native-customization-3382fd — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-native-customization-3382fd (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Teaches Claude how to change the behavior or appearance of the React Native UI Kit without modifying the kit itself. Four tiers, from cheapest to deepest:
Tier 1 — Props (95% of asks solved here)
Tier 2 — RequestBuilder (filter what data loads)
Tier 3 — Formatters + Templates (change how text / messages render)
Tier 4 — DataSource decorators + Events (last resort, powerful)Always try Tier 1 first. Escalate only when the tier can't do what the user wants.
Read `cometchat-native-components` first — the catalog is the source of truth for prop names, slot views, and event listener names that this skill builds on.
Ground truth: docs/ui-kit/react-native/custom-text-formatter-guide.mdx, mentions-formatter-guide.mdx, shortcut-formatter-guide.mdx, url-formatter-guide.mdx, events.mdx, methods.mdx, property-changes.mdx, and the kit's source at packages/ChatUiKit/src/shared/formatters/ and packages/ChatUiKit/src/shared/events/.
When a user says "I want X" for a CometChat component:
| If they want to... | Use Tier | Cost |
|---|---|---|
| Hide a feature (thread option, receipts, edit, etc.) | Tier 1 — hide* / *Visibility props | 1 line of JSX |
| Customize a subsection (header title, subtitle, avatar, empty state) | Tier 1 — <Slot>View prop | 1 component |
| Filter what loads (only show online users, exclude blocked, include tags) | Tier 2 — *RequestBuilder | 1 builder |
| Change how URLs / mentions / hashtags / emojis render inline | Tier 3 — textFormatters | Subclass of CometChatTextFormatter |
| Render a custom message type (custom bubble, custom interactive msg) | Tier 3 — templates + CometChatMessageTemplate | 1 template + 1 renderer |
| React to events from another component ("they deleted a message, now reload my view") | Tier 4 — CometChatUIEventHandler | Listener |
| Rewrite how data flows through the kit (custom conversation sorting, override user-fetch logic) | Tier 4 — DataSourceDecorator | Class extension |
If a user's ask fits Tier 1 but you jumped to Tier 3, you've written 50 lines that a 1-line prop could have replaced. Start low.
cometchat-native-components is the full catalog. Three prop families cover most customization:
hide* / *Visibility flagsTurn features off with a single prop:
<CometChatMessageList
user={selectedUser}
hideReplyInThreadOption // already mandatory — see components § 11
hideReceipts
hideReactions={false}
hideTranslateMessageOption
hideMessagePrivatelyOption
hideReplyOption={false}
/>Full list of hide* props per component: cometchat-native-components. Check there before writing custom code.
<Slot>View props — replace a sectionEvery component has PascalCase slot props for replacing named sections of its default UI:
<CometChatMessageHeader
user={selectedUser}
TitleView={(user, group) => <Text style={styles.customTitle}>{user?.getName()}</Text>}
SubtitleView={(user, group) => <OnlineStatus user={user} />}
LeadingView={(user, group) => <CustomAvatar user={user} />}
TrailingView={(user, group) => <CustomActions user={user} />}
AuxiliaryButtonView={(user, group) => <CometChatCallButtons user={user} group={group} />}
/>Slot functions receive the same data the default view would have (typically user, group, or a single entity). They return RN JSX.
For custom views that should match the theme, use useTheme():
import { useTheme } from "@cometchat/chat-uikit-react-native";
function CustomTitle({ user }: any) {
const theme = useTheme();
return (
<Text style={{
color: theme.color.textPrimary,
fontFamily: theme.typography.heading3.fontFamily,
fontSize: theme.typography.heading3.fontSize,
}}>
{user?.getName()}
</Text>
);
}See cometchat-native-theming § 8 for more on useTheme().
style={{ ... }} prop — nested stylingEach component accepts a nested-object style prop (see cometchat-native-components § 13):
<CometChatConversations
style={{
containerStyle: { backgroundColor: "#FAFAFA" },
itemStyle: {
avatarStyle: { containerStyle: { borderRadius: 8 } },
},
}}
/>Prefer theme-level changes (via cometchat-native-theming) for app-wide color shifts; use style={{}} only for one-off overrides on a single component instance.
For "I want to show a subset of X", use the matching *RequestBuilder. Never post-filter in-render.
import { CometChat } from "@cometchat/chat-sdk-react-native";
// Only conversations in a specific tag group
<CometChatConversations
conversationsRequestBuilder={
new CometChat.ConversationsRequestBuilder()
.setLimit(20)
.setUserTags(["premium"])
.setConversationType(CometChat.RECEIVER_TYPE.USER)
}
/>
// Only online users, exclude blocked
<CometChatUsers
usersRequestBuilder={
new CometChat.UsersRequestBuilder()
.setLimit(30)
.setStatus("online")
.setSearchKeyword("")
.friendsOnly(false)
}
/>
// Only groups you've joined
<CometChatGroups
groupsRequestBuilder={
new CometChat.GroupsRequestBuilder()
.setLimit(30)
.joinedOnly(true)
}
/>
// Message list — exclude system messages
<CometChatMessageList
user={user}
messageRequestBuilder={
new CometChat.MessagesRequestBuilder()
.setUID(user.getUid())
.setLimit(30)
.setCategories(["message"]) // exclude "call", "action"
.hideReplies(false)
}
hideReplyInThreadOption
/>Each request builder is chainable. The @cometchat/chat-sdk-react-native exports the builder classes — import them from the SDK, not the UI Kit.
Request builder methods are documented at cometchat.com/docs/sdk/react-native (or query the docs MCP). Common ones:
| Builder | Useful methods |
|---|---|
ConversationsRequestBuilder | .setLimit(n), .setUserTags([...]), .setGroupTags([...]), .setConversationType(type), .withTags(true), .withUserAndGroupTags(true) |
UsersRequestBuilder | .setLimit(n), .setStatus("online"), .setSearchKeyword(str), .friendsOnly(bool), .setTags([...]), .setUIDs([...]), .hideBlockedUsers(bool) |
GroupsRequestBuilder | .setLimit(n), .setSearchKeyword(str), .joinedOnly(bool), .setTags([...]), .setGroupTypes([...]) |
MessagesRequestBuilder | .setUID(uid) / .setGUID(guid), .setLimit(n), .setCategories([...]), .setTypes([...]), .hideReplies(bool), .setTags([...]), .setParentMessageId(id) |
GroupMembersRequestBuilder | .setLimit(n), .setSearchKeyword(str), .setScopes([...]) |
For "change how text or messages render", Tier 3 is the right level. Two sub-patterns:
CometChatTextFormatter is an abstract base class for matching inline text patterns (hashtags, keywords, emoji shortcodes, custom tags) and replacing them with custom JSX.
import {
CometChatTextFormatter,
SuggestionItem,
} from "@cometchat/chat-uikit-react-native";
import { CometChat } from "@cometchat/chat-sdk-react-native";
import { Text, View, StyleSheet } from "react-native";
class HashtagFormatter extends CometChatTextFormatter {
constructor() {
super();
this.setTrackingCharacter("#"); // optional — triggers suggestion list
this.setRegexPatterns([/\B#(\w+)\b/g]); // all matches get formatted
}
// Called for each bubble's text; return string | JSX
getFormattedText(
inputText: string | null | React.ReactNode,
): string | React.ReactNode {
if (typeof inputText !== "string") return inputText;
const parts = inputText.split(/(\B#\w+\b)/g);
return (
<Text>
{parts.map((part, i) =>
part.match(/^#\w+$/)
? <Text key={i} style={styles.hashtag} onPress={() => openHashtag(part)}>{part}</Text>
: <Text key={i}>{part}</Text>,
)}
</Text>
);
}
// Optional — called before a message is sent. Transform the outgoing message.
handlePreMessageSend(message: CometChat.TextMessage): CometChat.TextMessage {
// e.g. attach the list of hashtags to the message metadata
return message;
}
// Optional — for suggestion-list support (triggered by `#`)
search(searchKey: string): void {
// Fetch matching hashtags from your backend, then:
// this.setSearchData([{ id: "tag1", title: "#typescript" }]);
}
}
const styles = StyleSheet.create({
hashtag: { color: "#2563EB", fontWeight: "600" },
});Register the formatter by passing it to both CometChatMessageList and CometChatMessageComposer:
const formatters = [
new CometChatMentionsFormatter(), // keep the built-in ones
new CometChatUrlsFormatter(),
new HashtagFormatter(), // add yours
];
<CometChatMessageList
user={selectedUser}
textFormatters={formatters}
hideReplyInThreadOption
/>
<CometChatMessageComposer
user={selectedUser}
textFormatters={formatters}
/>For rendering a totally custom message type (interactive cards, scheduling, forms), use CometChatMessageTemplate.
import {
CometChatMessageTemplate,
CometChatUiKitConstants,
} from "@cometchat/chat-uikit-react-native";
const pollTemplate = new CometChatMessageTemplate({
type: "poll",
category: CometChatUiKitConstants.MessageCategoryConstants.custom,
ContentView: (message, alignment) => (
<PollBubble message={message} alignment={alignment} />
),
BottomView: (message, alignment) => (
<PollVoteCounts message={message} />
),
options: (loggedInUser, message, group) => [
/* CometChatMessageOption[] — custom long-press menu items */
],
});
<CometChatMessageList
user={selectedUser}
templates={[pollTemplate, ...defaultTemplates]} // merge with defaults
hideReplyInThreadOption
/>Getting the default templates to merge with:
import { ChatConfigurator } from "@cometchat/chat-uikit-react-native";
const defaults = ChatConfigurator.getDataSource().getAllMessageTemplates();
<CometChatMessageList templates={[pollTemplate, ...defaults]} />| Use formatter (Tier 3a) | Use template (Tier 3b) |
|---|---|
| Change how TEXT inside a bubble renders (hashtags, URLs, mentions, emoji shortcodes) | Render a completely different bubble body |
Content is still a TextMessage | Content is a custom message type (sent via CometChat.sendCustomMessage) |
| Doesn't need its own long-press options | Needs custom message options (vote, claim, accept, etc.) |
When Tiers 1-3 can't do it, you're modifying how data flows through the UI Kit. Two mechanisms:
CometChatUIEventHandlerSubscribe to events that UI Kit components emit so your own code can react.
import { CometChatUIEventHandler } from "@cometchat/chat-uikit-react-native";
import { useEffect } from "react";
function AppScreen() {
useEffect(() => {
const listenerId = "APP_MESSAGE_LISTENER";
CometChatUIEventHandler.addMessageListener(listenerId, {
ccMessageSent: ({ message, status }) => {
// status === "inProgress" | "sent"
analytics.track("message_sent", { id: message.getId() });
},
ccMessageEdited: ({ message }) => { /* ... */ },
ccMessageDeleted: ({ message }) => { /* ... */ },
ccMessageRead: ({ message }) => { /* ... */ },
ccLiveReaction: ({ reaction }) => { /* ... */ },
});
return () => CometChatUIEventHandler.removeMessageListener(listenerId);
}, []);
return /* ... */;
}| Listener | Use when... |
|---|---|
addMessageListener | reacting to any message-related event (sent, edited, deleted, read, reactions) |
addConversationListener | reacting to conversation-level events (ccConversationDeleted, ccUpdateConversation) |
addUserListener | reacting to user actions (ccUserBlocked, ccUserUnblocked) |
addGroupListener | reacting to group lifecycle (ccGroupCreated, ccGroupDeleted, ccGroupLeft, ccGroupMemberScopeChanged, ccGroupMemberKicked, ccGroupMemberBanned, ccGroupMemberJoined, ccGroupMemberAdded, ccOwnershipChanged, etc.) |
addCallListener | reacting to call events (onIncomingCallAccepted, onCallEnded, onCallInitiated, etc.) |
Every pair has a matching remove*Listener(id) — always call it in the cleanup of your `useEffect` to avoid duplicate listeners on re-render.
Listener ID uniqueness matters. Use a constant per component/feature. Colliding IDs cause only the latest-registered listener to fire.
DataSourceDecorator and MessageDataSource wrap the kit's internal data source to override specific methods without forking the whole kit.
When to reach for this: overriding how user data is fetched, how conversations are sorted, adding custom message metadata to every sent message, intercepting attachment uploads.
Minimum pattern:
import {
DataSource,
DataSourceDecorator,
ChatConfigurator,
} from "@cometchat/chat-uikit-react-native";
class MyDataSource extends DataSourceDecorator {
constructor(source: DataSource) {
super(source);
}
// Override only the method you want to change
getConversationsRequestBuilder() {
const builder = super.getConversationsRequestBuilder();
builder.setUserAndGroupTags(true);
return builder;
}
getMessageTemplate() {
const defaults = super.getMessageTemplate();
return [myCustomTemplate, ...defaults];
}
}
// Register the decorator before init — wraps the default data source
ChatConfigurator.dataSource = new MyDataSource(ChatConfigurator.getDataSource());
await CometChatUIKit.init(settings);This is an escape hatch, not a first tool. If you find yourself reaching for Tier 4, re-check whether Tier 1 (props) or Tier 3 (templates) could have solved it. Templates + slot views cover most "custom behavior" asks.
ExtensionsDataSource is the base class for registering an extension-shaped chunk of behavior (its own composer action + its own bubble + its own data handling) — this is what PollsExtension, StickersExtension, etc. extend internally. You'd only subclass this if you're shipping a reusable feature module across apps.
For a single app, use DataSourceDecorator instead.
Tier 2 — conversationsRequestBuilder with .setUserTags(["premium"]).
Tier 1 — EmptyStateView slot prop on CometChatUsers.
Tier 3b — CometChatMessageTemplate with a ContentView that branches on alignment === "receive". Or simpler — Tier 1 messageListStyles.receiveBubbleStyle in the theme (see cometchat-native-theming § 6).
Tier 3a — subclass CometChatMentionsFormatter (or extend CometChatTextFormatter), implement search(key) + setSearchData([...]) with your own suggestion source.
Tier 4a — CometChatUIEventHandler.addMessageListener with ccMessageSent handler.
Tier 4a — addGroupListener with ccGroupDeleted handler.
Tier 1 — LeadingView slot on CometChatConversations + CometChatUsers + CometChatMessageHeader.
Tier 1 — filter the attachmentOptions prop on CometChatMessageComposer:
<CometChatMessageComposer
user={user}
attachmentOptions={(user, group) => {
const defaults = /* default actions from ChatConfigurator */;
return defaults.filter((opt) => opt.id !== "attachment-file");
}}
/>Tier 2 — messageRequestBuilder with .setSearchKeyword("urgent").
Tier 3b — create a CometChatMessageTemplate with category: "custom" + type: "ping", render a custom ContentView, send via CometChat.sendCustomMessage.
Tier 4b — DataSourceDecorator overriding getConversationsRequestBuilder() + possibly wrapping the fetch itself. Rare. Try Tier 2 first.
CometChatMessageTemplate (Tier 3b) gives you full control over rendering + options without losing theming, reactions, typing, receipts.usersRequestBuilder.setStatus("online") — don't fetch everyone then hide rows.APP_MESSAGE_LISTENER or ${componentName}_MESSAGE_LISTENER — constant, unique. Colliding IDs silently drop earlier registrations.useMemo); re-creating them on every render loses the internal suggestion state.Say the user wants :emoji: shortcodes (e.g., :smile: → 😀):
// 1. Define the formatter — module scope, constructed once
import { CometChatTextFormatter } from "@cometchat/chat-uikit-react-native";
import { Text } from "react-native";
const EMOJI_MAP: Record<string, string> = {
":smile:": "😀", ":heart:": "❤️", ":thumbsup:": "👍", ":fire:": "🔥",
};
class EmojiShortcodeFormatter extends CometChatTextFormatter {
constructor() {
super();
this.setRegexPatterns([/:[a-z_]+:/g]);
}
getFormattedText(input: string | null | React.ReactNode) {
if (typeof input !== "string") return input;
const parts = input.split(/(:[a-z_]+:)/g);
return (
<Text>
{parts.map((p, i) =>
EMOJI_MAP[p] ? <Text key={i}>{EMOJI_MAP[p]}</Text> : <Text key={i}>{p}</Text>,
)}
</Text>
);
}
}
// 2. Build the formatters array at module scope
import { CometChatMentionsFormatter, CometChatUrlsFormatter } from "@cometchat/chat-uikit-react-native";
export const TEXT_FORMATTERS = [
new CometChatMentionsFormatter(),
new CometChatUrlsFormatter(),
new EmojiShortcodeFormatter(),
];
// 3. Wire into list + composer (same array — must match)
import { TEXT_FORMATTERS } from "./formatters";
<CometChatMessageList user={user} textFormatters={TEXT_FORMATTERS} hideReplyInThreadOption />
<CometChatMessageComposer user={user} textFormatters={TEXT_FORMATTERS} />| Skill | When to route |
|---|---|
cometchat-native-core | Init / login / provider chain |
cometchat-native-components | Prop reference — which hide*, <Slot>View, *RequestBuilder is available (prerequisite for Tiers 1–2) |
cometchat-native-placement | Where to put the customized components |
cometchat-native-theming | App-wide color / typography / dark mode — Tier 1 alternative to style={{}} |
cometchat-native-features | Which out-of-the-box features exist (so you know what needs customizing vs. what's already there) |
cometchat-native-customization | This skill — four-tier triage + custom formatters / templates / DataSource / events |
cometchat-native-production | When customization depends on production auth (token refresh, user-ID mapping) |
cometchat-native-troubleshooting | Formatter doesn't apply, listener fires twice, slot view renders nothing, template not showing |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.