React Native SDK
Drop-in mobile chat UI for iOS and Android, built on the same headless core as the web kit.
@cariosan/react-native is the mobile UI kit — native components (View/FlatList/TextInput) rendering the same chat experience as the web kit. It reuses the framework-agnostic core: @cariosan/client (transport) and the DOM-free hooks from @cariosan/react run unchanged in React Native, so engagement features (quoted reply, @-mentions, read receipts, search) behave identically to web.
What the kit gives you — and what you build
The kit ships building blocks, not whole screens. That's deliberate: chat UX is app-specific, and we don't want to drag a navigation library, a media picker, or Firebase into your bundle. The kit stays dependency-light; you bring your own platform pieces.
In the kit (@cariosan/react-native + @cariosan/client) — the engine + reusable UI:
- Components:
MessageList,MessageInput,ChannelList,TypingIndicator,PresenceBadge. - Hooks:
useMessages(with reconnect resync),useChannels,usePresence,useReadReceipts,useMessageActions,useMessageSelection,useReplyDraft, … - All the logic on
client/channel: send / edit / delete, reactions, receipts, search, @-mentions, attachment upload, group avatars + roles (updateChannel/addMembers/setMemberRole/removeMember), presence (withrole),lastMessagepreview.
You build (the app layer) — what's UX- or platform-specific:
- Screens + navigation — chat list, chat room, group info, etc. Use any router (Expo Router, React Navigation, or a simple state switch); the kit doesn't impose one.
- Auth — get a user JWT from your backend. (The example's "paste a JWT" login is a test shim, not production auth.)
- Attachment picker UI — the kit gives you the hooks (
MessageInput'sonAttachPress+attachmentPreview+onSendAttachment, pluschannel.uploadAttachment); you supply the picker with the library you prefer (expo-image-picker,react-native-image-picker, …). - Push — register the token via
client.registerDevice(...); the Firebase / native wiring is yours (BYO).
The example is a reference, not a dependency
examples/mobile-react-native wires all of the above into a full app (chat list, group info, member actions, attachment panel). Copy its screens as a starting point — but they live in the example, not in the published @cariosan/* packages. A real app composes the kit the same way, with its own design.
Install
Provider
Wrap your app once. You own the SDK lifecycle (construct, connect, pause on background) — the provider just shares the client:
Tokens. Pass a token provider (as above) instead of a fixed string, and create the client once. Do not rebuild it on renewal.
- Renewal before expiry. The SDK fetches a new token shortly before the current one expires and moves the open socket to it without reconnecting.
- Expired while backgrounded. JavaScript timers are suspended in the background, so the token may expire while the app sleeps. When the app wakes, the server refuses the old token; the SDK then asks the provider again and reconnects.
- Do not store the chat token. There is no need: AsyncStorage is readable by anything with access to the app's storage, and the provider can always fetch a new token.
See Tokens for the full behaviour.
Components
MessageList— aFlatListof messages in a familiar chat layout: day separators (Today / Yesterday / date), left/right bubbles (received in slate, your own right-aligned in emerald), bottom-right time + ✓/✓✓/✓✓-blue read-receipt ticks on your own messages, plus @-mention highlighting, quoted-reply previews (tap to scroll to the original), reaction pills, and edited/tombstone states. Inside aMessageSelectionProvider, long-press a message to highlight it and float a quick-reactions row (❤️ 👍 😂 …) above it. Props:currentUserId(internal id or external_id) decides which bubbles are "yours";showSenderNames={false}hides the sender name on received bubbles for 1:1 chats (it shows by default, which suits group channels). Your own messages show a 🕒 while they are being sent and Not sent with Retry / Delete if the send fails; passrenderOutgoingto draw those rows yourself. While a channel is on screen the list marks it delivered and read so the sender's ticks advance (markReadOnView, defaulttrue; passfalseif your app marks read elsewhere). The delivery ack travels over the WebSocket and is dropped while it is closed, so the list repeats it once the socket connects.MessageInput— text composer with an @-mention autocomplete (tap to insert; passmentionMembersor it lazily loads the roster), a 🙂 emoji inserter, a quote-reply chip, and a send button. PassonAttachPressto show a 📎 button — your handler picks a file and stages it viaattachmentPreview(a preview node) +onSendAttachment(caption), which fires when send is tapped (upload + send happen then, not on pick), e.g.channel.uploadAttachment(blob, { filename, mimeType })→sendMessage({ attachmentUrl }). InMessageList, image attachments render as thumbnails and other files as a tappable document card; emoji-only messages render large and bubble-less.ChannelList— the caller's channels with unread + mention badges.TypingIndicator,PresenceBadge— live presence/typing.
Hold-to-select: drive your own action bar
MessageList only renders the in-place highlight + quick-reactions. The contextual action bar (reply / copy / delete / …) is yours to render — read useMessageSelection() in your header and swap the title for actions while a message is held, WhatsApp-style:
deleteMessage is author-only (the server rejects deleting others' messages), so only show Delete when msg.user.id/external_id matches the current user. Copy needs a clipboard module — expo-clipboard works in Expo; the kit stays dependency-free.
Wrap your screen in a safe area
The kit components fill their parent and don't add device insets — that's the app's job. Without one, MessageInput's text field and send button sit behind the Android navigation bar (and the iOS home indicator) and can't be tapped. Use react-native-safe-area-context (bundled with Expo) — not react-native's SafeAreaView, which is iOS-only:
Hooks
The kit re-exports every hook from @cariosan/react (they're DOM-free) — useMessages, useMessageSender, useOutgoing, useChannels, useTyping, usePresence, useConnectionStatus, useReadReceipts, useMentions, useReplyDraft, useSearch, plus ChatProvider/useCariosan. Build fully custom RN UI on the hooks while reusing all the subscription/cursor plumbing. See the React hooks reference — the API is identical.
Styling
Components ship a sensible dark-slate default styled with StyleSheet. React Native has no CSS, so theming is via standard RN style props / forking the components — there's no stylesheet import like the web kit.
One core, every platform
@cariosan/client and the hooks are the single source of truth. The web (@cariosan/react) and mobile (@cariosan/react-native) kits differ only in their rendered components — so a feature shipped to the core lands on both.
Was this page helpful?