JS SDK — React Native (@loopchat/react-native)
React Native bindings: re-exports core + the react hooks (they're platform-neutral), replaces the DOM components with RN-native UI, and adds FCM push helpers.
- Repo:
js-sdk/packages/react-native. - Dependencies:
@loopchat/core,@loopchat/react(workspace). Peers:react ^18 || ^19,react-native >= 0.73. - Firebase messaging is a peer concern: the push helpers match
@react-native-firebase/messaging's shape without a hard dependency on it. - Tests: jest +
@testing-library/react-native; the Expo demo (js-sdk/examples/rn-demo) is the manual smoke target.
import {
Chat, Channel, MessageList, MessageInput,
registerPushDevice, parsePushPayload,
} from '@loopchat/react-native';
<Chat client={client}>
<Channel channel={channel}>
<MessageList />
<MessageInput keyboardVerticalOffset={headerHeight} />
</Channel>
</Chat>
What's re-exported vs native
- From
@loopchat/react: the<Chat>/<Channel>providers and every hook (useChatClient,useChannel,useStore,useMessages,useMembers,useCurrentUser,useUnreadCount,useChannelUnreadCount,useConnectionStatus,useChannelList). The DOM components stay in@loopchat/react. - From
@loopchat/core:LoopChatClient,Filter,devToken,LoopChatNetworkError,Store, and the common types (includingPushPayload). - RN-native UI (this package):
<MessageList>,<MessageInput>,<Avatar>.
RN-native components
| Component | Props | Behavior |
|---|---|---|
<MessageList> |
onMessagePress?(message) |
Inverted FlatList (windowed, keyboard-friendly) bound to the enclosing <Channel> — the RN equivalent of Flutter's LoopMessageListView. Own messages right-aligned, sender name + avatar for others, day separators, end-reach pagination for older history (channel.query()), and marks the channel read while mounted. |
<MessageInput> |
onMessageSent?(message), placeholder?, disabled?, keyboardVerticalOffset? |
Composer wrapped in KeyboardAvoidingView (iOS padding behavior): multiline TextInput, send button + send-on-submit, sending/disabled states, inline error display. keyboardVerticalOffset is forwarded (e.g. header height). |
<Avatar> |
name, image? |
Round avatar via Image, initials fallback. |
Push helpers
Wire FCM token acquisition and rotation to TheLoopChat device registration. PushTokenSource matches @react-native-firebase/messaging without depending on it:
interface PushTokenSource {
getToken(): Promise<string>;
onTokenRefresh?(listener: (token: string) => void): () => void;
}
| Function | Description |
|---|---|
registerPushDevice(client, source): Promise<() => void> |
Gets the current token, calls client.addDevice(token, 'firebase'), and keeps the registration fresh across token rotations (removes the previous token best-effort, registers the new one). Returns a cleanup function that stops listening for refreshes — it does not unregister the device; do that with unregisterPushDevice on logout. |
unregisterPushDevice(client, token) |
Removes a device registration (e.g. on logout). |
parsePushPayload(remoteMessage): PushPayload | null |
Parses an FCM data message (RemoteMessageLike — the shape @react-native-firebase/messaging hands to handlers) into the documented PushPayload (type/cid/channel_id/…), or null when the message is not a TheLoopChat push. |
import messaging from '@react-native-firebase/messaging';
const stop = await registerPushDevice(client, {
getToken: () => messaging().getToken(),
onTokenRefresh: (cb) => messaging().onTokenRefresh(cb),
});
messaging().onMessage((remoteMessage) => {
const push = parsePushPayload(remoteMessage);
if (push) navigateToChannel(push.channel_type, push.channel_id);
});
See API — Push and Webhooks for the payload contract and worker behavior.