Skip to content

refactor: introduce WithComponents context provider#3542

Open
oliverlaz wants to merge 16 commits intodevelopfrom
with-component-context
Open

refactor: introduce WithComponents context provider#3542
oliverlaz wants to merge 16 commits intodevelopfrom
with-component-context

Conversation

@oliverlaz
Copy link
Copy Markdown
Member

@oliverlaz oliverlaz commented Apr 8, 2026

🎯 Goal

Replace the prop-drilling of 120+ component overrides with a single WithComponents context provider. This eliminates massive boilerplate across Channel, ChannelList, and all consumer components.

Before:

<Channel Message={MyMessage} SendButton={MySendButton} Reply={MyReply} channel={channel}>

After:

<WithComponents overrides={{ Message: MyMessage, SendButton: MySendButton, Reply: MyReply }}>
  <Channel channel={channel}>
</WithComponents>

🛠 Implementation details

Design principle

All components are read from useComponentsContext(). All other contexts only provide data + APIs — never components.

New ComponentsContext

  • WithComponents provider — nestable, inner overrides win. Uses overrides prop (aligned with stream-chat-react)
  • useComponentsContext() hook — returns all components with defaults filled in
  • ComponentOverrides type — auto-derived from DEFAULT_COMPONENTS map (Partial<typeof DEFAULT_COMPONENTS>)
  • defaultComponents.ts — single source of truth for all ~120 default component mappings (lazy-loaded to avoid circular deps)
  • Adding a new overridable component only requires editing defaultComponents.ts — the type is derived automatically

What changed

  • Stripped component keys from MessagesContextValue, InputMessageInputContextValue, ChannelContextValue, ChannelsContextValue, AttachmentPickerContextValue, ThreadsContextValue, ImageGalleryContextValue — these now only carry data + APIs
  • Simplified useCreate*Context hooks — no longer receive or forward component params
  • Simplified Channel.tsx — removed ~90 component imports, prop defaults, and forwarding lines
  • Simplified ChannelList.tsx — removed ~19 component props
  • Updated ~80 consumer files — switched from old context hooks to useComponentsContext() for component reads
  • Removed component override props from ALL individual components (Chat, Thread, ThreadList, Poll, ChannelPreview, ImageGallery, etc.)
  • No component accepts component overrides as props anymore — everything goes through WithComponents
  • ComponentsContext.tsx reduced from ~350 lines to ~55 lines — type is derived from defaults, no manual type maintenance
  • Updated all 3 example apps (SampleApp, ExpoMessaging, TypeScriptMessaging) to use WithComponents

Net result

  • 90+ files changed, net -2500+ lines removed
  • Each component override name went from being listed 4 times to 0 in the forwarding pipeline
  • One place to override components: <WithComponents overrides={{ ... }}>
  • One place to read components: useComponentsContext()
  • One place to add new overridable components: defaultComponents.ts

BREAKING CHANGE

  • Component override props removed from all SDK components<Channel Message={X}>, <ChannelList Preview={X}>, <Thread MessageComposer={X}>, etc. no longer accept component overrides as props. Use <WithComponents overrides={{ Message: X }}> instead.
  • Component keys removed from context value typesMessagesContextValue, InputMessageInputContextValue, ChannelContextValue, ChannelsContextValue, AttachmentPickerContextValue, ThreadsContextValue, ImageGalleryContextValue no longer include component-type keys. Use useComponentsContext() to read component overrides.
  • List prop removed from ChannelList — use custom EmptyStateIndicator override or wrap ChannelListView directly.
  • LoadingIndicator prop removed from Chat — use <WithComponents overrides={{ ChatLoadingIndicator: X }}>.

🎨 UI Changes

No visual changes — this is a pure structural refactor.

🧪 Testing

  • yarn build — 0 type errors
  • yarn lint — passes clean (0 warnings, 0 errors)
  • yarn test:unit — 91/92 suites pass; 1 pre-existing timeout failure in offline-support that also fails on develop
  • New test: defaultComponents.test.ts verifies all default component mappings are defined and optional keys are explicitly listed
  • Updated test files to use <WithComponents overrides={...}> wrapper instead of removed component override props

☑️ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

@oliverlaz oliverlaz requested a review from isekovanic April 8, 2026 13:45
- Recreate defaultComponents.ts with all ~100 default component mappings
- Fix circular dependency in ComponentsContext by lazy-loading defaults
- Remove component override props from Attachment.tsx (use useComponentsContext)
- Update tests to use WithComponents wrapper instead of component override props
- Fix pre-existing ChannelList filter test (missing countUnread mock)
@Stream-SDK-Bot
Copy link
Copy Markdown
Contributor

Stream-SDK-Bot commented Apr 8, 2026

SDK Size

title develop branch diff status
js_bundle_size 351 KB 351 KB +301 B 🟡

Move Chat.LoadingIndicator, Thread.MessageComposer, ThreadList component
overrides, ChannelDetails.ChannelDetailsHeader, Poll component overrides,
ImageGallery.ImageGalleryVideoControls, and all ThreadsContext component
keys to ComponentsContext.

- Strip 7 component keys from ThreadsContextValue
- Strip ImageGalleryVideoControls from ImageGalleryContextValue
- Remove ChannelDetailsHeader prop from ChannelDetailsBottomSheet
- Remove component override props from all Poll components
- Update tests to use WithComponents wrapper
@oliverlaz oliverlaz changed the title refactor!: introduce WithComponents context provider refactor: introduce WithComponents context provider Apr 8, 2026
oliverlaz and others added 13 commits April 8, 2026 23:55
Migrate all component override props in the SampleApp to use the new
WithComponents context provider pattern:
- ChannelListScreen: HeaderNetworkDownIndicator, Preview
- ChannelScreen: AttachmentPickerSelectionBar, AttachmentPickerContent,
  MessageLocation, NetworkDownIndicator
- ThreadScreen: AttachmentPickerSelectionBar, MessageLocation
- NewDirectMessagingScreen: EmptyStateIndicator, SendButton
- SharedGroupsScreen: Preview, replace List prop with EmptyStateIndicator override
- Channel screen: MessageLocation and InputButtons moved to WithComponents
- Fix InputButtons type to use ComponentOverrides instead of Channel props
- Replace 300-line hand-written ComponentOverrides type with derived type:
  `Partial<(typeof import('./defaultComponents'))['DEFAULT_COMPONENTS']>`
- Remove ~110 type imports from ComponentsContext.tsx (now ~55 lines total)
- Add optional component entries to defaultComponents.ts for components
  with no default (MessageText, PollContent, Input, etc.)
- Remove remaining component override props from FileAttachment,
  StickyHeader, MessageBubble, MessageMenu
- Adding a new overridable component now only requires editing
  defaultComponents.ts — the type is auto-derived
Aligns with stream-chat-react's WithComponents API where the prop
is named `overrides` instead of `value`.
The shared LoadingIndicator key caused ChannelListView to use the
generic text-based indicator instead of the skeleton UI. Split into:
- ChannelListLoadingIndicator (default: skeleton UI)
- MessageListLoadingIndicator (default: text-based "Loading messages...")
Merge origin/develop into with-component-context.
- ChannelScreen/ThreadScreen: kept WithComponents pattern, dropped
  commented-out props from develop
- Channel.tsx: took develop's audioRecordingSendOnComplete=false default
- ChannelPreview files: merged specific import style from develop with
  useComponentsContext from our branch
- ChannelSwipableWrapper test: used develop's specific themeContext mock
- Fix WithComponents prop name (value -> overrides) in 6 test files
- Move ImageComponent from ChatContext to ComponentsContext with Image default
- Move ImageGalleryHeader/Footer/Grid from ImageGalleryContext to ComponentsContext
- Move MessageOverlayBackground from OverlayContext to ComponentsContext
- Update all consumer components to read from useComponentsContext()
- Clean up stale context values in tests
…rences

Component overrides are set once at mount and never change dynamically.
Remove useMemo dependencies so both the provider value and the hook
result are computed once, preventing unnecessary re-renders when
integrators pass inline override objects.
- Fix ImageComponent type in Reply.tsx (required, not optional)
- Replace React.ComponentType<any> with proper types (ImageProps,
  ImageGalleryHeaderProps, etc.) to satisfy no-explicit-any lint rule
- Add missing ImageProps imports in URLPreview/URLPreviewCompact
- Fix prettier formatting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants