This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Stream Chat React Native SDK monorepo. The main SDK code lives in package/ (published as stream-chat-react-native-core). Built on top of the stream-chat JS client library.
All commands below run from the repo root unless noted otherwise.
yarn build # Build all packages (runs in package/)
cd package && yarn build # Build SDK directlycd package && yarn lint # Check prettier + eslint + translation validation (max-warnings 0)
cd package && yarn lint-fix # Auto-fix lint and formatting issuescd package && yarn test:unit # Run all unit tests (sets TZ=UTC)
cd package && yarn test:coverage # Run with coverage report
cd package && TZ=UTC npx jest path/to/test.test.tsx # Run a single test fileTests use Jest with react-native preset and @testing-library/react-native. Test files live alongside source at src/**/__tests__/*.test.ts(x). Mock builders are in src/mock-builders/.
To run a single test, you can also temporarily add the file path to the testRegex array in package/jest.config.js.
yarn install --frozen-lockfile # Root dependencies
cd package && yarn install-all # SDK + native-package + expo-packagecd examples/SampleApp && yarn install
cd examples/SampleApp && yarn start # Metro bundler
cd examples/SampleApp && yarn ios # Run iOS
cd examples/SampleApp && yarn android # Run Androidpackage/β Main SDK (stream-chat-react-native-core)package/native-package/β React Native native module wrapperspackage/expo-package/β Expo-compatible wrapperexamples/SampleApp/β Full sample app with navigationrelease/β Semantic release scripts
Component hierarchy: <Chat> β <Channel> β <MessageList> / <MessageInput> / <Thread>
components/β UI components (~28 major ones: ChannelList, MessageList, MessageInput, Thread, Poll, ImageGallery, etc.)contexts/β React Context providers (~33 contexts). The primary way components receive state and callbacks. Key contexts:ChatContext,ChannelContext,MessagesContext,ThemeContext,TranslationContexthooks/β Custom hooks (~27+). Access contexts viauseChannelContext(),useMessageContext(), etc.state-store/β Client-side state stores usinguseSyncExternalStorewith selector pattern (audio player, image gallery, message overlay, etc.)store/β Offline SQLite persistence layer.OfflineDBclass with mappers for channels, messages, reactions, members, drafts, reminders. Schema instore/schema.tstheme/β Deep theming system (colors, typography, spacing, per-component overrides) viaThemeContexti18n/β Internationalization with i18next (14 languages).Streami18nwrapper classmiddlewares/β Command UI middlewares (attachments, emoji)icons/β SVG icon components
Component override pattern: Nearly every UI element is replaceable via props. Parent components (e.g., Channel) accept 50+ React.ComponentType props for sub-components (Message, MessageContent, DateHeader, TypingIndicator, etc.). These props are forwarded into Context providers so deeply nested children can access them without prop drilling.
Context three-layer pattern: Each context follows the same structure:
createContext()with a sentinel default value (DEFAULT_BASE_CONTEXT_VALUE)- A
<XProvider>wrapper component - A
useXContext()hook that throws if used outside the provider (suppressed in test env viaisTestEnvironment())
Context values are assembled in dedicated useCreateXContext() hooks (e.g., useCreateChannelContext) that carefully memoize with selective dependencies to avoid unnecessary re-renders.
Native module abstraction: native.ts defines TypeScript interfaces for all platform-specific capabilities (image picking, compression, haptics, audio/video, clipboard). Implementations are injected at runtime via registerNativeHandlers() β stream-chat-expo provides Expo implementations, stream-chat-react-native provides bare RN ones. Calling an unregistered handler throws with a message to import the right package.
State stores: useSyncExternalStore-based stores in state-store/ with useStateStore(store, selector) for fine-grained subscriptions outside the context system.
Memoization: Components use React.memo() with custom areEqual comparators (not HOCs) to prevent re-renders.
Offline-first: SQLite-backed persistence with sync status tracking and pending task management.
Builder-bob builds: Outputs CommonJS (lib/commonjs), ESM (lib/module), and TypeScript declarations (lib/typescript).
Tests use renderHook() and render() from @testing-library/react-native. Components/hooks must be wrapped in the required provider stack (e.g., Chat β Channel β feature provider).
Mock builders (src/mock-builders/):
api/initiateClientWithChannels.jsβ creates a test client + channels in one callgenerator/β factories:generateMessage(),generateChannel(),generateUser(),generateMember(),generateStaticMessage(seed)(deterministic via UUID v5)attachments.jsβgenerateImageAttachment(),generateFileAttachment(),generateAudioAttachment()
Reanimated and native modules are mocked via Proxy patterns in test setup files.
Themes follow a three-tier token architecture: Primitives (raw colors) β Semantics (e.g., colors.error.primary) β Components (per-component overrides). Token references use $key string syntax (e.g., "$blue500") and are resolved via topological sort in theme/topologicalResolution.ts, so declaration order doesn't matter.
Platform-specific tokens are generated files in src/theme/generated/{light,dark}/StreamTokens.{ios,android,web}.ts β regenerate via sync-theme.sh if design tokens change; don't hand-edit.
Custom themes are passed as style prop to <Chat>. mergeThemes() deep-merges custom style over base theme (deep-cloned via JSON.parse(JSON.stringify())). Light/dark mode is auto-detected via useColorScheme().
native-package/ and expo-package/ are thin wrappers around stream-chat-react-native-core. They:
- Call
registerNativeHandlers()with platform-specific implementations (native modules vs Expo APIs) - Export optional dependency wrappers (
Audio,Video,FlatList) fromsrc/optionalDependencies/ - Re-export everything from core:
export * from 'stream-chat-react-native-core'
Platform branching uses runtime Platform.select() / Platform.OS checks β there are no .ios.ts / .android.ts source file splits.
<Chat client={client}> is the entry point. It:
- Sets SDK metadata on the
stream-chatclient (identifier, device info) - Disables the JS client's
recoverStateOnReconnect(the SDK handles recovery itself) - Registers subscriptions for threads, polls, and reminders (cleaned up on unmount)
- Initializes
OfflineDBifenableOfflineSupportis true - Wraps children in:
ChatProviderβTranslationProviderβThemeProviderβChannelsStateProvider
SQLite schema is in store/schema.ts. DB versioning uses PRAGMA user_version β a version mismatch triggers full DB reinit (no incremental migrations). Current version is tracked in SqliteClient.dbVersion.
Translation JSON files live in src/i18n/. validate-translations (run as part of yarn lint) checks that no translation key has an empty string value. When adding/updating translations, run yarn build-translations (i18next-cli sync) to keep files in sync.
- Conventional commits enforced by commitlint:
feat:,fix:,docs:,refactor:, etc. - ESLint 9 flat config at
package/eslint.config.mjs, strict (max-warnings 0) - Prettier: single quotes, trailing commas, 100 char width (see
.prettierrc) - TypeScript strict mode with platform-specific module suffixes (
.ios,.android,.web) - Git branches: PRs target
develop,mainis production releases only - Shared native sync: Run
yarn shared-native:syncfrompackage/after modifying shared native code to copy to native-package and expo-package