React hooks and providers for Tinode chat on React Native and the web, on top of the official JavaScript SDK, tinode-sdk.
- iOS, Android and web. Works in Expo Go and in bare React Native.
- No native code.
- TypeScript types for the whole SDK: protocol messages, topics, Drafty documents and contact cards.
- Offline cache: IndexedDB on the web;
expo-sqlite, MMKV or AsyncStorage on native, in one line.
yarn add react-native-tinodetinode-sdk comes with it. Until the next tinode-sdk release is on npm, it is installed from a pinned commit of
the tinode-js devel branch, so installing needs git and npm on the
machine and access to github.com (all three are on CI runners and EAS build images). Networks that block git or
GitHub cannot install it until then.
import {
TinodeProvider,
useConnection,
useLogin,
useMeTopic,
} from 'react-native-tinode';
export default function App() {
return (
<TinodeProvider
config={{
appName: 'MyApp/1.0',
host: 'api.tinode.co',
apiKey: 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K',
secure: true,
}}
>
<Main />
</TinodeProvider>
);
}
function Main() {
const { status } = useConnection();
const { loginBasic, user } = useLogin();
const { contacts } = useMeTopic();
}A complete app (login, contacts, chat with paging and typing indicator) lives in example/.
To point it at a local Tinode server, which comes with test
users like alice/alice123:
EXPO_PUBLIC_TINODE_HOST=localhost:6060 EXPO_PUBLIC_TINODE_SECURE=false yarn example startOn the Android emulator the example rewrites localhost to 10.0.2.2.
Tinode is the tinode-sdk client, so its documentation applies.
import { createTinode } from 'react-native-tinode';
const tinode = createTinode({ appName: 'MyApp/1.0', host: 'api.example.com', apiKey: '...' });
await tinode.connect();
await tinode.loginBasic('alice', 'secret');
const me = tinode.getMeTopic();
me.onContactUpdate = (what, contact) => console.log(what, contact.name);
await me.subscribe(me.startMetaQuery().withLaterSub().withDesc().build());createTinode fills in platform from Platform.OS. You can also use new Tinode(config) yourself.
| Hook | Returns |
|---|---|
useTinode() |
the Tinode instance |
useConnection() |
{ status, retryIn, error }, status is disconnected, connecting, connected or authenticated |
useLogin() |
{ loginBasic, loginToken, logout, user, token, error } |
useMeTopic() |
{ contacts, isSubscribed, error } |
useTopic(name) |
{ topic, isSubscribed, desc, subscribers, subscribe, leave, error } |
useMessages(name, { pageSize }) |
{ messages, hasMore, loading, loadMore, send, status, markRead, error } |
useTyping(name) |
{ typingUsers, notifyTyping } |
Several components can use the same topic. The subscription is shared and the topic is left shortly after the last one unmounts.
Tinode callbacks such as onData hold a single function. The hooks do not overwrite your own handlers: listeners are
added through attach(target, 'onData', fn), which you can use too.
useLogin keeps the auth token in memory only. To stay signed in across launches, see
docs/persistence/auth-token.md.
The provider reconnects when the app comes back to the foreground. When the connection drops, the client retries with a growing delay. To retry at once when the network comes back, pass NetInfo in the config:
import NetInfo from '@react-native-community/netinfo';
<TinodeProvider config={{ ...config, netInfo: NetInfo }}>netInfo is anything with addEventListener(listener) that returns an unsubscribe function and calls the listener
with { isConnected }, so a small wrapper over the browser online and offline events works on the web too.
By default nothing is cached (persist: false). With a cache, your app can show chats at once on start, and chats
opened before stay readable offline.
| Your app | Store | Guide |
|---|---|---|
| Expo (Expo Go or a development build) | expo-sqlite/kv-store |
docs/persistence/expo-sqlite.md |
| Bare React Native | react-native-mmkv |
docs/persistence/mmkv.md |
| Already uses AsyncStorage | @react-native-async-storage/async-storage |
docs/persistence/async-storage.md |
| Web | nothing to install, persist: true uses IndexedDB |
docs/persistence/README.md |
import Storage from 'expo-sqlite/kv-store';
import { createTinode, KeyValueAdapter } from 'react-native-tinode';
createTinode({ ...config, persist: true, storage: new KeyValueAdapter(Storage) });The store is your app's dependency, not this library's. To keep users signed in across launches, see docs/persistence/auth-token.md. How the cache works, sharing a store and limits: docs/persistence/README.md.
tinode.getLargeFileHelper() uploads and downloads attachments.
upload()accepts a webBlob/Fileor a React Native file object{ uri, name, type }.download()saves the file in the browser. It does not work on native yet: the currenttinode-sdkneeds a DOM for it. Until the fix is released upstream, download attachments yourself withfetchand the auth token.
import messaging from '@react-native-firebase/messaging';
import { registerDeviceToken } from 'react-native-tinode';
registerDeviceToken(tinode, await messaging().getToken());With Expo, use (await Notifications.getDevicePushTokenAsync()).data. The server sends FCM data messages; how to
show them is up to the app.
- Providers and hooks with shared topic subscriptions, paging and typing state.
- TypeScript types.
tinode-sdkis plain JavaScript and ships no types. - Two small polyfills for Hermes, installed only when missing:
Intl.Segmenter(grapheme level only, used by Drafty) and a UTF-8TextDecoder(used by vCard import). Importreact-native-tinodebeforetinode-sdkso they are in place when the SDK loads. isCommError(error), becausetinode-sdkdoes not export itsCommErrorclass.
See docs/DECISIONS.md for the reasoning.
Apache-2.0. Uses tinode-sdk by Tinode LLC, see NOTICE.