Info:
This is release candidate documentation for Stream Chat React Native SDK v10. For the latest stable version, see the latest version (v9).

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:

  • StreamingMessageView renders Markdown as it streams in, with a typewriter effect, syntax highlighting, tables and charts.
  • ComposerView is 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. SpeechToTextButton is its dictation button.
  • AITypingIndicatorView shows a state of the model, such as "Thinking", as shimmering text.
  • Markdown, SyntaxHighlighter and VictoryChart are the renderers StreamingMessageView uses.
  • useStreamingMessage drives the typewriter effect, useDictation the speech to text, and isDictationAvailable() tells whether dictation is part of the build.
  • StreamTheme provides the theme of these components, which useTheme from 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-skia and victory-native, so an app that imports it installs both.
  • Babel. Its zod dependency uses export * as syntax, which the React Native Babel preset doesn't transform. Add @babel/plugin-transform-export-namespace-from to babel.config.js, before react-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 = true in the Podfile, before use_native_modules!, and add NSMicrophoneUsageDescription and NSSpeechRecognitionUsageDescription to Info.plist. On Android, add the RECORD_AUDIO permission to your app's AndroidManifest.xml. With Expo, add the stream-chat-expo config plugin to app.json, which does both. On iOS without the opt-in, SpeechToTextButton renders nothing and isDictationAvailable() returns false.
  • Media and clipboard. The composer's media picker uses react-native-image-picker or expo-image-picker, and the copy button of code blocks uses @react-native-clipboard/clipboard or expo-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:

nativeHandlers.ts
import {
  defaultNativeHandlers,
  registerNativeHandlers,
} from "stream-chat-react-native";

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:

nativeHandlers.ts
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:

nativeHandlers.ts
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-native or stream-chat-expo, never from stream-chat-react-native-core, so the native handlers are registered.
  • Import ai-components only 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 defaultNativeHandlers rather than rewriting it, and keep its signature.
  • Install only the optional packages of the features you use, and add the permissions each one needs.