Ground truth:
@cometchat/chat-uikit-react-native@5+ catalogrn-v5.json(216 public symbols) +features.rn-v5.json. Official docs: https://www.cometchat.com/docs/ui-kit/react-native/overview · scoped index…/ui-kit/react-native/llms-react-native-v5.md. Verify every symbol against the catalog; fetch signatures from docs (references/docs-map.md); never trust memory.⚠️ React Native is NOT React. Different package, different API, native views instead of DOM. A React UI Kit snippet will not compile here.
references/docs-map.mdends with the verified React → React Native name table — read it before porting anything.
Use this skill when
- "add chat to my React Native app" · "integrate CometChat in Expo" · "RN chat UI"
- any React Native CometChat task — it is the assumed base for every other
cometchat-react-native-*skill
Prerequisites & install
One package per platform. Never mix @cometchat/chat-uikit-react (web) into an RN app.
bashnpm install @cometchat/chat-uikit-react-native@5 @cometchat/chat-sdk-react-native@4
Then the native peer deps — required, not optional. Verified against 5.4.0 + the integration pages:
bashnpm install @react-native-clipboard/clipboard \ react-native-gesture-handler react-native-svg react-native-video react-native-localize \ react-native-safe-area-context @react-native-async-storage/async-storage@^2.1.2 dayjs # Expo also needs: punycode
cd ios && pod install for bare RN. A missing native module fails at MOUNT, not at build — the
error names a NativeModule, not CometChat, so it reads as unrelated.
Setup & credentials (essentials — full detail: references/setup-credentials.md)
- Detect the project: Expo (
app.json/app.config.js) or bare RN (metro.config.js). Reuse an existing.cometchat/config.jsonor env if present — skip re-setup. - version_conflict — STOP if
@cometchat/chat-uikit-react-nativeis already inpackage.jsonat a non-v5 major; reconcile first, never mix majors (RULES.md). - Credentials — OFFER the dashboard fetch FIRST (never silently "paste it yourself" — AUDIT-039). Offer both, defaulting to fetch: (a) dashboard fetch (recommended) —
npx @cometchat/skills-cli@3 auth login, pick an EXISTING app (provision list --json; never auto-create),provision use --app-id <id> --jsonwrites.cometchat/config.json; (b) manual paste from Dashboard → Credentials (dev-only Auth Key). Then the SKILL writes the env (§4) — the CLI stops at the fetch; env-writing and codegen are the SKILL's job. Full flow:references/setup-credentials.md. - Write env: bare RN →
.env(read viareact-native-config); Expo →.env(read viaConstants.expoConfig.extraorprocess.env). Gitignore it; never echo the Auth Key. Prod → server-minted auth token, not the Auth Key. Nothing in a mobile bundle is secret (IPA/APK are readable) — seereferences/setup-credentials.md. - Authorize =
initFromSettingsthenloginboth resolve; auth error is almost always the wrong Region.
Integration ordering (BAKED — invariant)
init() must resolve before login(). login() must resolve before you render any component.
Breaking it gives a blank screen, not an error.
Use initFromSettings, not the classic init — it persists integrationSource="ai-agent"
so CometChat can attribute the integration. The classic init does not (AUDIT-084), and a machine
gate enforces this.
tsximport { CometChatUIKit } from "@cometchat/chat-uikit-react-native"; import { CometChat } from "@cometchat/chat-sdk-react-native"; const settings: CometChat.CometChatSettings = { appId: APP_ID, region: REGION, credentials: { authKey: AUTH_KEY }, // dev only — production mints an auth token server-side chatSDK: { presenceSubscription: { type: "ALL_USERS" }, autoEstablishSocketConnection: true, }, }; await CometChatUIKit.initFromSettings(settings); await CometChatUIKit.login({ uid: UID }); // or ({ authToken }) in production
Note the shape: presence moves into chatSDK.presenceSubscription.type — it is not a
top-level field and not a builder call.
⚠️ Two shapes that differ from React and will not compile if ported:
- settings is a plain object — there is no
UIKitSettingsBuilderexport in RN logintakes an object:login({ uid }), notlogin(uid)
Use an existing UID from your dashboard (the sample apps seed cometchat-uid-1). Never invent one.
The provider chain (BAKED — order is load-bearing)
Outermost first. Verified against the integration + recipe pages:
tsx<GestureHandlerRootView style={{ flex: 1 }}> <SafeAreaProvider> <CometChatI18nProvider> <SafeAreaView style={{ flex: 1 }}> <CometChatThemeProvider> {/* screens */} </CometChatThemeProvider> </SafeAreaView> </CometChatI18nProvider> </SafeAreaProvider> </GestureHandlerRootView>
GestureHandlerRootView is outermost and must carry flex: 1. Omit it and swipe / long-press
silently do nothing — no error, no warning. It is the single most common "the UI renders but nothing
responds" cause on RN.
Sizing — flex, never viewport units
There is no 100dvh and no CSS. The rule: every ancestor of a kit component is flex: 1, and a
scrolling column also needs minHeight: 0. A fixed pixel height breaks when the soft keyboard opens.
The composer must remain visible with the keyboard up — verify on a device, not only a simulator.
Component / API map (BAKED closed list — from catalogs/rn-v5.json)
| Need | Component |
|---|---|
| conversation list | CometChatConversations |
| message pane | CometChatMessageHeader · CometChatMessageList · CometChatMessageComposer |
| compact composer | CometChatCompactMessageComposer |
| threads | CometChatThreadHeader |
| search | CometChatSearch |
| users / groups / members | CometChatUsers · CometChatGroups · CometChatGroupMembers |
| calls | CometChatCallButtons · CometChatIncomingCall · CometChatOutgoingCall · CometChatOngoingCall · CometChatCallLogs |
| theming / i18n | CometChatThemeProvider + useTheme() · CometChatI18nProvider |
| extension chain | ChatConfigurator + DataSourceDecorator |
Anything not in catalogs/rn-v5.json does not exist — do not emit it. Notably absent in v5:
CometChatErrorBoundary, CometChatDetails, CometChatAddMembers, CometChatBannedMembers,
CometChatTransferOwnership, and every *WithMessages composite. See references/docs-map.md
for the SDK-fallback path for each.
Callback names — RN uses Press, not Click
| Component | Wire these |
|---|---|
CometChatConversations | onItemPress · onSearchBarClicked · onError |
CometChatMessageList | onThreadRepliesPress · onError |
CometChatMessageHeader | showBackButton (default false) + onBack · onError |
CometChatSearch | onBack · onConversationClicked · onMessageClicked |
React's onItemClick / onThreadRepliesClick do not exist here.
⚠️ On
CometChatGroupMembers,onErrortakes NO argument in kits up to 5.5.0 —onError?: () => void, unlike every other list. Type the handler with a parameter there and it fails with TS2322. WriteonError={() => …}and show the failure with theErrorViewslot, which needs no error object. Fixed in the kit (uikit-react-native#1452); once that ships, the parameter is available and this note goes away.
Golden path — the production-ready core surface
The default for an unscoped "add chat". One screen per route on a stack navigator with a real back button — never two panes side by side, and never a web-style side panel.
tsx// ChatScreen.tsx — the message pane import { CometChatMessageHeader, CometChatMessageList, CometChatMessageComposer, } from "@cometchat/chat-uikit-react-native"; import { CometChat } from "@cometchat/chat-sdk-react-native"; import { View } from "react-native"; export const ChatScreen = ({ user, group, onBack }: { user?: CometChat.User; group?: CometChat.Group; onBack: () => void; }) => ( <View style={{ flex: 1 }}> {/* showBackButton defaults to FALSE on RN — without it onBack never fires */} <CometChatMessageHeader user={user} group={group} showBackButton onBack={onBack} /> <CometChatMessageList user={user} group={group} /> <CometChatMessageComposer user={user} group={group} /> </View> );
tsx// ConversationsScreen.tsx — the list, wired to open the pane import { CometChatConversations, CometChatUiKitConstants } from "@cometchat/chat-uikit-react-native"; import { CometChat } from "@cometchat/chat-sdk-react-native"; export const ConversationsScreen = ({ onOpen }: { onOpen: (user?: CometChat.User, group?: CometChat.Group) => void; }) => ( <CometChatConversations onItemPress={(conversation: CometChat.Conversation) => { const isUser = conversation.getConversationType() === CometChatUiKitConstants.ConversationTypeConstants.user; isUser ? onOpen(conversation.getConversationWith() as CometChat.User, undefined) : onOpen(undefined, conversation.getConversationWith() as CometChat.Group); }} /> );
The core surface is FOUR things, not two
contracts.rn-v5.json → core-chat-surface mandates all four by default, and the reviewer's G1
gate fails an emit that ships fewer. A list ↔ message pane is half the surface:
| Capability | Component | Wire |
|---|---|---|
| conversations | CometChatConversations | onItemPress |
| messages | CometChatMessageHeader + List + Composer | showBackButton + onBack |
| threaded replies | CometChatThreadHeader | onThreadRepliesPress → push a thread screen |
| search | CometChatSearch | showSearchBar={true} + onSearchBarClicked → push a search screen |
On RN both are pushed screens, not side panels. Search is opt-out only, and must cover the global (list) case and the in-chat case scoped to the open conversation.
tsx// message pane — thread entry point <CometChatMessageList user={user} group={group} onThreadRepliesPress={(message) => openThread(message)} /> // conversation list — search entry point. // showSearchBar defaults to FALSE on RN (it is true on React), so the callback alone // silently ships a surface with no search. Both props, always. <CometChatConversations onItemPress={open} showSearchBar={true} onSearchBarClicked={() => openSearch()} />
The callbacks above are only the entry points. openThread and openSearch push screens, and
those screens are where the two mandated components actually render — emit both, or the surface
has tap targets that lead nowhere:
tsx// ThreadScreen — what openThread(message) pushes to. // parentMessageId is typed `string` on the list and `string | number` on the composer, // so the list needs .toString(). function ThreadScreen({ route, navigation }) { const { parentMessage, user, group } = route.params; return ( <View style={{ flex: 1, minHeight: 0 }}> <CometChatThreadHeader parentMessage={parentMessage} /> <CometChatMessageList user={user} group={group} parentMessageId={parentMessage.getId().toString()} /> <CometChatMessageComposer user={user} group={group} parentMessageId={parentMessage.getId()} /> </View> ); }
tsx// SearchScreen — what openSearch() pushes to. // Omit uid/guid for the GLOBAL case (from the conversation list); pass one to scope the // search to the open conversation. Both cases are mandated — one screen serves both. function SearchScreen({ route, navigation }) { const { user, group } = route.params ?? {}; return ( <CometChatSearch onBack={() => navigation.goBack()} uid={user?.getUid()} guid={group?.getGuid()} onConversationClicked={(conversation) => navigation.navigate("Messages", { conversation })} onMessageClicked={(message) => navigation.navigate("Messages", { message })} onError={(e) => console.error(e)} /> ); }
DOCS GAP (RN-G1): there is no
components/searchpage for React Native —CometChatSearchis exported and mentioned only inside a task guide. Build from the catalog and flag it; do not conclude search is unavailable.
Every affordance that is visible by default is wired or hidden. A tap target that does nothing is
a defect, and so is an opened surface with no way back — if you open search or a thread, wire its
onBack in the same edit.
Deep references (load only when the task needs them)
references/docs-map.md— intent → the exact docs page; the SDK-fallback table; the React→RN name table. Read before porting any React snippet.references/component-props.md— baked props + PascalCase view slots per drop-in, and the defaults that differ from React (showSearchBarisfalsehere). Read before placing custom UI — it goes in a slot, never as a sibling.references/anti-patterns.md— the 13 failures that actually break an RN integration, each with why its error names the wrong thing.references/lifecycle.md—initFromSettings→ login → render, and the promise-cached boot guard (a boolean flag does not work).references/layout.md— the four sizing invariants thecore-chat-surfacecontract gates on.references/dependencies.md— the exact peer set + the native config that is not optional (Podfile modular headers, gesture-handler import order).references/setup-credentials.md— dev vs production auth, and why nothing in a mobile bundle is secret.
Common pitfalls
- Missing
GestureHandlerRootView— gestures silently dead. Outermost,flex: 1. - Rendering before
login()resolves — blank screen, no error. - Porting React code —
UIKitSettingsBuilder,login(uid),onItemClick, CSS,document.*: none exist here. - Fixed heights — breaks on keyboard open. Use
flex: 1+minHeight: 0. - Shipping the Auth Key — dev only; production mints an auth token server-side.
Verify it works
- Every emitted symbol appears in
catalogs/rn-v5.json. - Code compiles against the pinned kit:
npm run verify:fences:rn-v5. - Oracles agree with the kit and docs:
npm run verify:sync:rn-v5. - On a device: chat fills the screen, the composer survives the keyboard, back works, gestures respond.
Explain what you built (REQUIRED close)
Tell the user briefly: (1) what I wired (3–5 bullets, NAME the files); (2) decisions & why, flagging dev-only AS dev-only (the Auth Key is dev-only → server-minted auth token for production; the <uid> you used; Expo vs bare RN path); (3) what I did NOT touch (additive — navigation/auth/styles intact). Then offer these THREE options as a selectable choice and WAIT — do not auto-continue:
- ① Add another feature — check what's already wired; ASK which Dashboard extensions are enabled; suggest 3–4 gaps (calls · push · AI smart replies · polls · stickers · translation · notification feed). Never suggest reactions or mentions — they are ON by default in v5. →
cometchat-react-native-features/cometchat-react-native-calls/cometchat-react-native-push. - ② Customize theming — ask for brand color / light-dark →
cometchat-react-native-customization. - ③ Test it manually — hand it back so they run it on a device.

