Skip to content

Latest commit

Β 

History

History
163 lines (105 loc) Β· 9.42 KB

File metadata and controls

163 lines (105 loc) Β· 9.42 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Repository Overview

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.

This is a Yarn 4 (Berry) workspace monorepo. The Yarn binary lives in .yarn/releases/yarn-4.15.0.cjs and is invoked via yarnPath in .yarnrc.yml; any globally-installed Yarn launcher (e.g. the Homebrew Yarn 1.x) auto-delegates to it. No Corepack required.

Workspaces: package, package/native-package, package/expo-package, examples/SampleApp, examples/ExpoMessaging, examples/TypeScriptMessaging. There is a single root yarn.lock.

Common Commands

All commands below run from the repo root.

Install

yarn install                  # Set up every workspace (single root lockfile)
yarn install --immutable      # CI-style; fail if yarn.lock would change

The root package/'s postinstall runs husky install and yarn shared-native:sync automatically.

Build

yarn build                                          # SDK build (commonjs + esm + types)
yarn workspace stream-chat-react-native-core build  # Same, explicit form

Lint & Format

yarn lint        # prettier + eslint + translation validation (max-warnings 0)
yarn lint-fix    # Auto-fix lint and formatting issues

Test

yarn test:unit                                          # All unit tests (sets TZ=UTC)
yarn test:coverage                                      # With coverage report
yarn workspace stream-chat-react-native-core test:unit  # Same as `yarn test:unit`
cd package && TZ=UTC npx jest path/to/test.test.tsx     # Single test file

Tests 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.

Sample App

yarn workspace sampleapp start    # Metro bundler (alias: cd examples/SampleApp && yarn start)
yarn workspace sampleapp ios      # Run iOS
yarn workspace sampleapp android  # Run Android

Architecture

Package Structure

  • package/ β€” Main SDK (stream-chat-react-native-core)
  • package/native-package/ β€” React Native native module wrappers
  • package/expo-package/ β€” Expo-compatible wrapper
  • examples/SampleApp/ β€” Full sample app with navigation
  • release/ β€” Semantic release scripts

SDK Source (package/src/)

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, TranslationContext
  • hooks/ β€” Custom hooks (~27+). Access contexts via useChannelContext(), useMessageContext(), etc.
  • state-store/ β€” Client-side state stores using useSyncExternalStore with selector pattern (audio player, image gallery, message overlay, etc.)
  • store/ β€” Offline SQLite persistence layer. OfflineDB class with mappers for channels, messages, reactions, members, drafts, reminders. Schema in store/schema.ts
  • theme/ β€” Deep theming system (colors, typography, spacing, per-component overrides) via ThemeContext
  • i18n/ β€” Internationalization with i18next (14 languages). Streami18n wrapper class
  • middlewares/ β€” Command UI middlewares (attachments, emoji)
  • icons/ β€” SVG icon components

Key Patterns

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:

  1. createContext() with a sentinel default value (DEFAULT_BASE_CONTEXT_VALUE)
  2. A <XProvider> wrapper component
  3. A useXContext() hook that throws if used outside the provider (suppressed in test env via isTestEnvironment())

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).

Testing Patterns

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 call
  • generator/ β€” 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.

Theme System

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 / Expo Package Relationship

native-package/ and expo-package/ are thin wrappers around stream-chat-react-native-core. They:

  1. Call registerNativeHandlers() with platform-specific implementations (native modules vs Expo APIs)
  2. Export optional dependency wrappers (Audio, Video, FlatList) from src/optionalDependencies/
  3. 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 Component (Root Provider)

<Chat client={client}> is the entry point. It:

  • Sets SDK metadata on the stream-chat client (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 OfflineDB if enableOfflineSupport is true
  • Wraps children in: ChatProvider β†’ TranslationProvider β†’ ThemeProvider β†’ ChannelsStateProvider

Offline DB

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.

Translations

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.

Conventions

  • 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, main is production releases only
  • Shared native sync: Root yarn install's postinstall runs yarn shared-native:sync automatically. Re-run manually with yarn workspace stream-chat-react-native-core shared-native:sync after modifying package/shared-native/.
  • No Lerna: the release pipeline uses yarn workspaces foreach directly. Release-participating workspaces (core SDK + SampleApp) are hardcoded in release/release.config.js.