Entry points and native handlers
An app built with React Native CLI imports its components from stream-chat-react-native. An Expo app imports the same components from stream-chat-expo. Both packages re-export one core SDK, and each registers the native handlers of its toolchain, the functions and components that pick images, play audio or copy text with the packages you install. An app that shows AI responses also imports from the ai-components entry point of its package.
This page lists the entry points, what they export and which packages they need, and every native handler. Installation has the short version.
| Entry point | Toolchain | What it's for | Native handlers it registers |
|---|---|---|---|
stream-chat-react-native |
React Native CLI | Components, contexts and hooks | Every handler, from the RN CLI packages |
stream-chat-expo |
Expo | Components, contexts and hooks | Every handler, from the Expo packages |
stream-chat-react-native/ai-components |
React Native CLI | AI components, such as streaming messages, Markdown and the AI composer | NativeShimmerView and PerfText |
stream-chat-expo/ai-components |
Expo | AI components, such as streaming messages, Markdown and the AI composer | NativeShimmerView and PerfText |
Install one of the two packages, never both. Metro doesn't drop unused code, so the AI components are a separate entry point, and the main entry point never imports from it. An app that never imports ai-components doesn't bundle them, or the packages they use.
stream-chat-react-native and stream-chat-expo
These are the core UI. Both packages export the same components, contexts and hooks, from the core package stream-chat-react-native-core, which they depend on. Import from your toolchain's package, not from stream-chat-react-native-core. Importing the core alone registers no native handlers. Each package also exports defaultNativeHandlers, the handlers it registers. See Overriding a handler.
The packages don't re-export stream-chat. Import the client and the state classes, such as StreamChat and ChannelPaginator, from stream-chat itself.
The main exports by area:
| Area | Main exports | Explained in |
|---|---|---|
| Setup | Chat, OverlayProvider, useCreateChatClient, useChatContext, Streami18n, useTranslationContext |
Chat |
| Reading state | useStateStore, useChannel, useMessagePaginator, useChannelContext, useMessageContext |
Reading state in components |
| Channel list | ChannelList, ChannelListView, ChannelPreview, ChannelPreviewView, useChannelsContext |
Channel list UI |
| Channel | Channel, KeyboardCompatibleView, useOwnCapabilitiesContext |
Channel UI |
| Message list | MessageList, MessageFlashList, Message, MessageItemView, Attachment, Gallery, ReactionListTop, TypingIndicator |
Message list UI |
| Message composer | MessageComposer, AttachmentPicker, AutoCompleteSuggestionList, useMessageComposer, useMessageInputContext |
Message composer UI |
| Threads | Thread, ThreadList, useThreadContext, useThreadsContext |
Thread UI |
| Channel details | ChannelDetails, useChannelDetailsContext, useChannelActions, useUserActions |
Channel details and members |
| Overriding components | WithComponents, useComponentsContext and the icon components, such as Mute and SendRight |
Overriding components |
| Actions | messageActions, useMessageActions, useChannelActionItems |
Actions |
| Notifications | NotificationList, useNotifications, useClientNotifications, useInAppNotificationsState |
Displaying notifications |
| Overlays | BottomSheetModal, ImageGallery, useOverlayContext, PortalWhileClosingView |
Overlays and bottom sheets |
| Theming | useTheme, ThemeProvider, mergeThemes, defaultTheme |
Theming |
| Connection | useWSConnectionState, useNetworkConnectionState |
Connection status |
| Voice and audio | AudioRecorder, AudioAttachment, useAudioPlayer |
Voice recordings and audio playback |
| Polls | Poll, CreatePoll, usePollContext |
Polls |
| AI state | AITypingIndicatorView, useAIState, StreamingMessageView |
SDK integration |
| Accessibility | useAccessibilityContext, useAccessibilityAnnouncer |
Accessibility |
| Offline support | OfflineDB, SqliteClient, SqliteClientError, OfflineStoreApis |
Offline support |
| Native handlers | registerNativeHandlers, defaultNativeHandlers |
Native handlers |
Reference, Hooks lists the hooks by state area, starting with State and entity hooks. Every component's props are in the Component reference, starting with Chat.
stream-chat-react-native/ai-components and stream-chat-expo/ai-components
The entry point exports the components for AI chat:
StreamingMessageViewrenders Markdown as it streams in, with a typewriter effect, syntax highlighting, tables and charts.ComposerViewis a composer with attachment previews, a bottom sheet of options, speech to text, and a send button that turns into a stop button while a response is generated.SpeechToTextButtonis its dictation button.AITypingIndicatorViewshows a state of the model, such as "Thinking", as shimmering text.Markdown,SyntaxHighlighterandVictoryChartare the renderersStreamingMessageViewuses.useStreamingMessagedrives the typewriter effect,useDictationthe speech to text, andisDictationAvailable()tells whether dictation is part of the build.StreamThemeprovides the theme of these components, whichuseThemefrom this entry point reads. Wrap the AI components in it.
The entry point doesn't import the main entry point, so it doesn't register the main native handlers. It registers only the two native views its components use, NativeShimmerView and, on Android, PerfText, which renders highlighted code as one native text view. Importing both entry points registers them twice, which is harmless.
Several names exist in both entry points, as different exports. For example, AITypingIndicatorView, StreamingMessageView and useStreamingMessage from the main entry point belong to the chat UI. The theme exports of this entry point, Theme, useTheme, defaultTheme, mergeThemes and DeepPartial, belong to StreamTheme, not to the chat theme.
What the entry point needs:
- Charts. It imports
@shopify/react-native-skiaandvictory-native, so an app that imports it installs both. - Babel. Its
zoddependency usesexport * assyntax, which the React Native Babel preset doesn't transform. Add@babel/plugin-transform-export-namespace-fromtobabel.config.js, beforereact-native-worklets/plugin. - Dictation. On iOS, the dictation module is compiled into the app only when you opt in, because App Store Connect asks every app whose binary references the Speech framework for a speech recognition usage description. With React Native CLI, set
$StreamChatEnableDictation = truein thePodfile, beforeuse_native_modules!, and addNSMicrophoneUsageDescriptionandNSSpeechRecognitionUsageDescriptiontoInfo.plist. On Android, add theRECORD_AUDIOpermission to your app'sAndroidManifest.xml. With Expo, add thestream-chat-expoconfig plugin toapp.json, which does both. On iOS without the opt-in,SpeechToTextButtonrenders nothing andisDictationAvailable()returnsfalse. - Media and clipboard. The composer's media picker uses
react-native-image-pickerorexpo-image-picker, and the copy button of code blocks uses@react-native-clipboard/clipboardorexpo-clipboard, whichever is installed. These don't go through the native handlers.
See the AI integrations overview and SDK integration.
Native handlers
The core SDK doesn't import any native package itself. It calls native handlers, a function or a component for each platform capability, such as pickImage, setClipboardString or Video. stream-chat-react-native and stream-chat-expo each register an implementation of every handler when they're first imported, built on the packages of their toolchain.
Each implementation checks at import time whether its package is installed. When it isn't, the package registers null for most handlers, and the SDK leaves out the feature. For example, without takePhoto the composer has no camera button, without shareImage the image gallery has no share button, and without setClipboardString the message menu has no copy action. triggerHaptic is the exception. Without its package, it's a function that does nothing.
Handlers
| Handler | What it does | stream-chat-react-native |
stream-chat-expo |
|---|---|---|---|
Audio |
Records voice messages, and plays a recording back before it's sent | react-native-nitro-sound, or the deprecated react-native-audio-recorder-player, with react-native-blob-util |
expo-audio, or the deprecated expo-av |
overrideAudioRecordingConfiguration |
Receives the recording configuration of Audio and returns the one to use. It isn't kept as a handler |
Returns the configuration unchanged | Returns the configuration unchanged |
compressImage |
Compresses an image before upload, when compressImageQuality is set on Channel to a value other than 1 |
Built in. It always writes JPEG, over a white background | expo-image-manipulator |
deleteFile |
Deletes the copy of an attachment that was downloaded to share it | react-native-blob-util |
expo-file-system |
FlatList |
The FlatList component MessageList renders. A FlatList prop on Channel takes precedence |
React Native's FlatList |
React Native's FlatList |
getLocalAssetUri |
Resolves a photo library asset to a local file. The SDK checks that it's registered before it shows the media grid. The native uploader uses the package's own implementation, not the registered one | @react-native-camera-roll/camera-roll |
expo-media-library |
getPhotos |
Pages through the photo library, for the media grid of the attachment picker | @react-native-camera-roll/camera-roll |
expo-media-library |
iOS14RefreshGallerySelection |
Opens the iOS picker that changes which photos a limited library grants | @react-native-camera-roll/camera-roll |
expo-media-library |
oniOS14GalleryLibrarySelectionChange |
Subscribes to changes of that selection | @react-native-camera-roll/camera-roll |
expo-media-library |
multipartUpload |
Uploads files with the SDK's native uploader, when useNativeMultipartUpload is set on Chat |
Built in | Built in |
NativeShimmerView |
The shimmer of loading placeholders | Built in | Built in |
PerfText |
The native text view of highlighted code in the AI components, on Android | Built in, registered by the ai-components entry point |
Built in, registered by the ai-components entry point |
pickDocument |
Opens the system file picker | @react-native-documents/picker |
expo-document-picker |
pickImage |
Opens the system image picker, for attachments and the channel image | react-native-image-picker |
expo-image-picker |
saveFile |
Downloads an attachment to the cache directory, to share it | react-native-blob-util |
expo-file-system |
SDK |
The name of the package, which the SDK reports to Stream as the SDK identifier | "stream-chat-react-native" |
"stream-chat-expo" |
setClipboardString |
Copies a message's text | @react-native-clipboard/clipboard |
expo-clipboard |
shareImage |
Opens the system share sheet for an attachment in the image gallery | react-native-share |
expo-sharing |
Sound |
Plays audio attachments and voice recordings | react-native-nitro-sound, or the deprecated react-native-audio-recorder-player |
expo-audio, or the deprecated expo-av |
takePhoto |
Opens the camera, for attachments and the channel image | react-native-image-picker |
expo-image-picker |
triggerHaptic |
Plays haptic feedback, on long presses, in the audio recorder and in the image gallery | react-native-haptic-feedback |
expo-haptics |
Video |
Plays videos in the image gallery | react-native-video |
expo-video |
Where a package is listed with a fallback, the handler uses the first package that is installed. A few handlers need more than one package to turn a feature on. The share button of the image gallery needs shareImage and a file system handler, saveFile or deleteFile. The media grid of the attachment picker needs getPhotos, getLocalAssetUri and both iOS 14 handlers.
Overriding a handler
registerNativeHandlers() takes an object with the handlers to replace, and keeps the others. Call it once, at the top level of a module, after the import of stream-chat-react-native or stream-chat-expo, so it runs after the package has registered its defaults. Calls inside a component run again on every render.
To change what a default handler does, wrap it. defaultNativeHandlers holds the handlers the package registered, with null for most of those whose package isn't installed. This override makes the camera button take photos only, never videos:
import {
defaultNativeHandlers,
registerNativeHandlers,
} from "stream-chat-react-native";
const defaultTakePhoto = defaultNativeHandlers.takePhoto;
if (defaultTakePhoto) {
registerNativeHandlers({
takePhoto: (options) =>
defaultTakePhoto({ ...options, mediaType: "image" }),
});
}import {
defaultNativeHandlers,
registerNativeHandlers,
} from "stream-chat-expo";
const defaultTakePhoto = defaultNativeHandlers.takePhoto;
if (defaultTakePhoto) {
registerNativeHandlers({
takePhoto: (options) =>
defaultTakePhoto({ ...options, mediaType: "image" }),
});
}Import the file once, for example from the file that renders OverlayProvider. The SDK reads a handler each time it uses it, so an override applies from then on. Register overrides before the first render, so every part of the UI uses them.
To replace a handler, register your own function with the same signature. For example, to turn off haptic feedback:
import { registerNativeHandlers } from "stream-chat-react-native";
registerNativeHandlers({
triggerHaptic: () => {},
});With React Native CLI, wrapping compressImage is also how you set the color painted behind transparent images. The SDK passes only the quality, the size and the URI, and the default handler paints white unless it gets backgroundColor, a color string or an RGBA integer:
import {
defaultNativeHandlers,
registerNativeHandlers,
} from "stream-chat-react-native";
const defaultCompressImage = defaultNativeHandlers.compressImage;
if (defaultCompressImage) {
registerNativeHandlers({
compressImage: (params) =>
defaultCompressImage({ ...params, backgroundColor: "#000000" }),
});
}Pass backgroundColor: null to leave the background to the platform. stream-chat-expo has no such option.
overrideAudioRecordingConfiguration changes the recording settings of the registered Audio. It receives the current configuration and returns the new one, which the SDK sets on Audio when you register it. Without a registered Audio, it has no effect. The configuration types are RNCLIAudioRecordingConfiguration and ExpoAudioRecordingConfiguration.
Handler types
The parameter of registerNativeHandlers() types every handler. The SDK doesn't export a type for each one, so derive it:
import type { registerNativeHandlers } from "stream-chat-react-native";
type NativeHandlers = Parameters<typeof registerNativeHandlers>[0];
type TakePhoto = NonNullable<NativeHandlers["takePhoto"]>;PickImageOptions, PlaybackStatus, RecordingStatus and SoundReturnType, the types of the handlers' data, are exported too.
Optional packages
The core SDK and each package declare their optional packages as optional peer dependencies. Install one only if you use the feature that needs it. The required peers are on Installation.
Both toolchains:
| Package | Version | Needed by |
|---|---|---|
@op-engineering/op-sqlite |
>=14.0.0 |
Offline support, with enableOfflineSupport on Chat |
@shopify/flash-list |
>=2.1.0 |
MessageFlashList |
@shopify/react-native-skia |
>=2.0.0 |
The ai-components entry point, for charts |
victory-native |
>=41.0.0 |
The ai-components entry point, for charts |
emoji-mart |
>=5.4.0 |
Nothing in the SDK imports it. Its SearchIndex can feed createTextComposerEmojiMiddleware |
@emoji-mart/data |
>=1.1.0 |
Nothing in the SDK imports it. The emoji data for emoji-mart |
stream-chat-react-native:
| Package | Version | Needed by |
|---|---|---|
react-native-video |
>=6.18.0 |
Video |
react-native-nitro-sound |
>=0.2.9 |
Audio and Sound. It needs react-native-nitro-modules |
react-native-audio-recorder-player |
>=3.6.13 |
Audio and Sound, deprecated. Install react-native-nitro-sound instead |
react-native-blob-util |
>=0.22.0 |
Audio, saveFile and deleteFile |
react-native-share |
>=11.0.0 |
shareImage |
react-native-haptic-feedback |
>=2.3.0 |
triggerHaptic |
@react-native-clipboard/clipboard |
>=1.16.0 |
setClipboardString |
@react-native-documents/picker |
>=10.1.1 |
pickDocument |
react-native-image-picker |
>=7.1.2 |
pickImage and takePhoto |
@react-native-camera-roll/camera-roll |
>=7.9.0 |
getPhotos, getLocalAssetUri and the iOS 14 handlers |
stream-chat-expo needs Expo SDK 53 or later. Its packages take any version:
| Package | Needed by |
|---|---|
expo-image-manipulator |
compressImage. stream-chat-expo imports it when it loads, so install it |
expo-video |
Video |
expo-audio |
Audio and Sound |
expo-av |
Audio and Sound, deprecated. Install expo-audio instead |
expo-file-system |
saveFile and deleteFile |
expo-sharing |
shareImage |
expo-haptics |
triggerHaptic |
expo-clipboard |
setClipboardString |
expo-document-picker |
pickDocument |
expo-image-picker |
pickImage and takePhoto |
expo-media-library |
getPhotos, getLocalAssetUri and the iOS 14 handlers |
Most of these are native modules, so rebuild the app after adding one.
Best practices
- Import from
stream-chat-react-nativeorstream-chat-expo, never fromstream-chat-react-native-core, so the native handlers are registered. - Import
ai-componentsonly if the app renders AI components. One import anywhere in the app bundles them, with the packages they need. - Register handler overrides once, at the top level of a module, before the first render.
- Wrap the default handler from
defaultNativeHandlersrather than rewriting it, and keep its signature. - Install only the optional packages of the features you use, and add the permissions each one needs.