Skip to content

Commit 31ef8c8

Browse files
committed
docs: update PLAN.md with current state and reference notes
1 parent c0f6234 commit 31ef8c8

1 file changed

Lines changed: 103 additions & 137 deletions

File tree

  • package/src/contexts/componentsContext
Lines changed: 103 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,146 @@
1-
# Plan: `WithComponents` Context Provider
2-
3-
## Context
4-
5-
The SDK prop-drills 120+ component overrides through `<Channel>` β†’ `useCreate*Context` hooks β†’ context values. Each component name is listed **4 times** (destructured from props β†’ passed to hook β†’ destructured in hook β†’ listed in useMemo). Consumers then read components back out via `useMessagesContext()`, `useMessageInputContext()`, etc.
6-
7-
**Goal**: Replace this entire pipeline with a single `ComponentsContext`. Component overrides are **removed** from all existing contexts. Consumers read components via `useComponentsContext()` instead.
8-
9-
```tsx
10-
// User API
11-
<WithComponents value={{ Message: MyMessage, SendButton: MySendButton }}>
12-
<Channel channel={channel}>
13-
<MessageList />
14-
<MessageInput />
15-
</Channel>
16-
</WithComponents>
17-
```
1+
# WithComponents β€” Component Override System
182

193
## Design Principle
204

215
**All components are read from `useComponentsContext()`. All other contexts only provide data + APIs β€” never components.**
226

23-
No context besides `ComponentsContext` should carry component references. This is the single rule that drives every change in this plan.
7+
## Current State (Completed)
248

25-
## Architecture
9+
### What was done
2610

27-
### Before
11+
1. **Created `ComponentsContext`** β€” `WithComponents` provider, `useComponentsContext()` hook, `ComponentOverrides` type
12+
2. **Created `defaultComponents.ts`** β€” centralized map of all ~130 default components
13+
3. **Stripped component keys** from all existing context types: `MessagesContextValue`, `InputMessageInputContextValue`, `ChannelContextValue`, `ChannelsContextValue`, `AttachmentPickerContextValue`, `ThreadsContextValue`, `ImageGalleryContextValue`
14+
4. **Simplified `useCreate*Context` hooks** β€” no longer receive or forward component params
15+
5. **Simplified `Channel.tsx`** β€” removed ~90 component imports, prop defaults, forwarding lines
16+
6. **Simplified `ChannelList.tsx`** β€” removed ~19 component props
17+
7. **Updated ~80 consumer files** β€” switched from old context hooks to `useComponentsContext()`
18+
8. **Removed component override props** from ALL individual components
19+
9. **Updated all 3 example apps** (SampleApp, ExpoMessaging, TypeScriptMessaging)
20+
10. **Updated ~45 documentation pages** across docs-content repo
21+
11. **Merged with develop** and resolved conflicts
2822

29-
```
30-
Channel props (90+ component overrides with defaults)
31-
β†’ useCreateMessagesContext (receives 60+ component params, maps into useMemo)
32-
β†’ MessagesContext carries components + runtime data
33-
β†’ Consumer: const { Message } = useMessagesContext()
34-
```
35-
36-
### After
23+
### Architecture
3724

3825
```
39-
DEFAULT_COMPONENTS (static map)
40-
β†’ ComponentsContext (defaults; user overrides via WithComponents)
41-
β†’ Consumer: const { Message } = useComponentsContext()
42-
43-
Channel props (runtime/config only)
44-
β†’ useCreateMessagesContext (runtime data only, no components)
45-
β†’ MessagesContext carries ONLY data + APIs
46-
β†’ Consumer: const { deleteMessage } = useMessagesContext()
26+
User: <WithComponents overrides={{ Message: Custom }}>
27+
↓
28+
ComponentsContext (merges parent + overrides, inner wins)
29+
↓
30+
useComponentsContext() β†’ { ...DEFAULT_COMPONENTS, ...overrides }
31+
↓
32+
Consumer: const { Message } = useComponentsContext()
4733
```
4834

49-
## Scope
50-
51-
### What changes
52-
53-
1. **Existing context types** (`MessagesContextValue`, `InputMessageInputContextValue`, `ChannelContextValue`, `ChannelsContextValue`, `AttachmentPickerContextValue`) β€” remove all component-type keys
54-
2. **`useCreate*Context` hooks** β€” remove all component params, stop mapping them
55-
3. **Channel.tsx** β€” remove ~90 component imports, ~90 destructuring defaults, ~90 forwarding lines
56-
4. **ChannelList.tsx** β€” remove ~19 component props and forwarding
57-
5. **~117 consumer callsites across ~97 files** β€” switch from `useXContext()` to `useComponentsContext()` for component reads
58-
59-
### What doesn't change
35+
### Key Files
6036

61-
- Runtime data flow (callbacks like `deleteMessage`, `sendReaction`, state like `targetedMessage`) stays in existing contexts
62-
- Consumer reads of runtime data (`const { deleteMessage } = useMessagesContext()`) are untouched
63-
- `WithComponents` nesting semantics (inner wins, like standard React context)
37+
| File | Purpose |
38+
|------|---------|
39+
| `ComponentsContext.tsx` | ~60 lines. `ComponentOverrides` type (derived from `typeof DEFAULT_COMPONENTS`), `WithComponents` provider, `useComponentsContext()` hook |
40+
| `defaultComponents.ts` | ~300 lines. Single source of truth for all default component mappings. Adding a new component here auto-extends `ComponentOverrides` |
6441

65-
## New Files
42+
### Type System
6643

67-
| File | Purpose |
68-
| -------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
69-
| `package/src/contexts/componentsContext/ComponentsContext.tsx` | `ComponentOverrides` type, `WithComponents` provider, `useComponentsContext()` hook |
70-
| `package/src/contexts/componentsContext/defaultComponents.ts` | All default component imports β†’ `DEFAULT_COMPONENTS` map |
71-
72-
Both already drafted in the repo.
73-
74-
## Implementation Steps
75-
76-
### Step 1: Finalize `ComponentsContext.tsx` and `defaultComponents.ts`
77-
78-
Already drafted. Key design:
79-
80-
- `ComponentOverrides`: flat map, all keys optional, explicitly typed per component
81-
- Context default = `DEFAULT_COMPONENTS` β†’ `useComponentsContext()` always returns resolved values
82-
- `WithComponents`: merges `{ ...parent, ...value }` (inner wins)
83-
- `ResolvedComponents` = `Required<ComponentOverrides>` for the return type
84-
85-
Special cases:
86-
87-
- `FlatList` β€” from `NativeHandlers.FlatList` at runtime. Keep as a runtime prop in MessagesContext, not in ComponentsContext.
88-
- `StopMessageStreamingButton` β€” can be `null` (explicitly hide). The type in ComponentOverrides allows `| null`.
44+
`ComponentOverrides` is derived automatically:
45+
```ts
46+
export type ComponentOverrides = Partial<
47+
(typeof import('./defaultComponents'))['DEFAULT_COMPONENTS']
48+
>;
49+
```
8950

90-
### Step 2: Strip component keys from existing context value types
51+
No manual type maintenance β€” add a component to `DEFAULT_COMPONENTS` and the type updates.
9152

92-
**`MessagesContextValue`** (`package/src/contexts/messagesContext/MessagesContext.tsx`):
93-
Remove ~60 component keys (Attachment, AudioAttachment, DateHeader, Message, MessageContent, Reply, etc.). Keep runtime keys only (deleteMessage, deleteReaction, dismissKeyboardOnMessageTouch, giphyVersion, messageContentOrder, etc.).
53+
### Circular Dependency Handling
9454

95-
**`InputMessageInputContextValue`** (`package/src/contexts/messageInputContext/MessageInputContext.tsx`):
96-
Remove ~35 component keys (AttachButton, AudioRecorder, SendButton, Input, InputView, etc.). Keep runtime keys only (asyncMessagesLockDistance, audioRecordingEnabled, editMessage, sendMessage, etc.).
55+
`defaultComponents.ts` β†’ imports components β†’ components import `useComponentsContext` from `ComponentsContext.tsx`.
9756

98-
**`ChannelContextValue`** (`package/src/contexts/channelContext/ChannelContext.tsx`):
99-
Remove 4 component keys (EmptyStateIndicator, LoadingIndicator, NetworkDownIndicator, StickyHeader).
57+
Broken by lazy-loading defaults in the hook:
58+
```ts
59+
let cachedDefaults: ComponentOverrides | undefined;
60+
const getDefaults = () => {
61+
if (!cachedDefaults) {
62+
cachedDefaults = require('./defaultComponents').DEFAULT_COMPONENTS;
63+
}
64+
return cachedDefaults;
65+
};
66+
```
10067

101-
**`ChannelsContextValue`** (`package/src/contexts/channelsContext/ChannelsContext.tsx`):
102-
Remove ~19 component keys (Preview, PreviewAvatar, PreviewMessage, Skeleton, FooterLoadingIndicator, etc.).
68+
### Naming Conventions
10369

104-
**`AttachmentPickerContextValue`** (`package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx`):
105-
Remove 3 component keys (ImageOverlaySelectedComponent, AttachmentPickerSelectionBar, AttachmentPickerContent).
70+
Some component keys differ from their default component names to avoid collisions:
10671

107-
### Step 3: Simplify `useCreate*Context` hooks
72+
| Override Key | Default Component | Why renamed |
73+
|---|---|---|
74+
| `FileAttachmentIcon` | `FileIcon` | Clarity |
75+
| `ChannelListLoadingIndicator` | `ChannelListLoadingIndicator` | Split from shared `LoadingIndicator` β€” renders skeleton UI |
76+
| `MessageListLoadingIndicator` | `LoadingIndicator` | Split from shared `LoadingIndicator` β€” renders text |
77+
| `ChatLoadingIndicator` | `undefined` | Optional, no default |
78+
| `ThreadMessageComposer` | `MessageComposer` | Avoid collision with `MessageComposer` component name |
79+
| `ThreadListComponent` | `DefaultThreadListComponent` | Avoid collision with exported `ThreadList` |
80+
| `StartAudioRecordingButton` | `AudioRecordingButton` | Historical naming |
81+
| `Preview` | `ChannelPreviewView` | ChannelList preview item |
82+
| `PreviewAvatar` | `ChannelAvatar` | ChannelList preview avatar |
83+
| `FooterLoadingIndicator` | `ChannelListFooterLoadingIndicator` | ChannelList footer |
84+
| `HeaderErrorIndicator` | `ChannelListHeaderErrorIndicator` | ChannelList header |
85+
| `HeaderNetworkDownIndicator` | `ChannelListHeaderNetworkDownIndicator` | ChannelList header |
10886

109-
Each hook drops all component params and stops mapping them into useMemo:
87+
### Optional Components (no default)
11088

111-
- **`useCreateMessagesContext`** (`package/src/components/Channel/hooks/useCreateMessagesContext.ts`): ~60 component params removed, keep ~30 runtime params
112-
- **`useCreateInputMessageInputContext`** (`package/src/components/Channel/hooks/useCreateInputMessageInputContext.ts`): ~35 component params removed, keep ~15 runtime params
113-
- **`useCreateChannelContext`** (`package/src/components/Channel/hooks/useCreateChannelContext.ts`): 4 component params removed
114-
- **`useCreateChannelsContext`** (`package/src/components/ChannelList/hooks/useCreateChannelsContext.ts`): ~19 component params removed, keep ~20 runtime params
89+
These exist in `DEFAULT_COMPONENTS` as `undefined` with `React.ComponentType<any> | undefined` type assertions:
11590

116-
### Step 4: Simplify Channel.tsx
91+
`AttachmentPickerIOSSelectMorePhotos`, `ChatLoadingIndicator`, `CreatePollContent`, `ImageComponent`, `Input`, `ListHeaderComponent`, `MessageContentBottomView`, `MessageContentLeadingView`, `MessageContentTopView`, `MessageContentTrailingView`, `MessageLocation`, `MessageSpacer`, `MessageText`, `PollContent`
11792

118-
- Remove ~90 default component imports (lines 114-223)
119-
- Remove component keys from `ChannelPropsWithContext` type
120-
- Remove component destructuring defaults from `ChannelWithContext`
121-
- Remove component values from `useCreateMessagesContext()`, `useCreateInputMessageInputContext()`, `useCreateChannelContext()` calls
122-
- Remove component values from `attachmentPickerContext` useMemo
123-
- `LoadingErrorIndicator` and `KeyboardCompatibleView` are used directly in Channel's JSX β€” read from `useComponentsContext()` or keep as Channel-specific props
93+
### Shared Component Keys (audited)
12494

125-
### Step 5: Simplify ChannelList.tsx
95+
Some keys were used in multiple contexts before the refactor. Audit results:
12696

127-
- Remove component keys from `ChannelListProps` type
128-
- Remove default component imports
129-
- Remove component values from `useCreateChannelsContext()` call
97+
| Key | Used By | Same Default? | Resolution |
98+
|-----|---------|---------------|------------|
99+
| `EmptyStateIndicator` | Channel + ChannelList | Yes (differentiates via `listType` prop) | Single key βœ… |
100+
| `LoadingErrorIndicator` | Channel + ChannelList | Yes (differentiates via `listType` prop) | Single key βœ… |
101+
| `LoadingIndicator` | Channel + ChannelList | **No** β€” Channel used text-based, ChannelList used skeleton | Split into `MessageListLoadingIndicator` + `ChannelListLoadingIndicator` βœ… |
130102

131-
### Step 6: Update ~117 consumer callsites
103+
### API Alignment with stream-chat-react
132104

133-
Switch component destructuring from old context hooks to `useComponentsContext()`:
105+
| Aspect | React Native | React Web |
106+
|--------|-------------|-----------|
107+
| Provider | `WithComponents` | `WithComponents` |
108+
| Prop name | `overrides` | `overrides` |
109+
| Hook | `useComponentsContext()` | `useComponentContext()` |
110+
| Type | `ComponentOverrides` (auto-derived) | `ComponentContextValue` (hand-written) |
111+
| Defaults | Lazy-loaded via `require()` | Set at `Channel` level |
112+
| Merge | `useMemo` | Plain spread (no memo) |
134113

135-
```tsx
136-
// Before
137-
const { Message, MessageStatus, MessageTimestamp } = useMessagesContext();
138-
const { deleteMessage } = useMessagesContext();
114+
## Known Issues / Future Work
139115

140-
// After
141-
const { Message, MessageStatus, MessageTimestamp } = useComponentsContext();
142-
const { deleteMessage } = useMessagesContext();
143-
```
116+
### Pre-existing Test Failures (not caused by this work)
144117

145-
**Key files by volume** (largest consumers):
118+
These test suites fail on `develop` too:
119+
- `offline-support/index.test.ts` β€” timeout
120+
- `ChannelList.test.js` β€” filter race condition (`channel.countUnread` mock missing)
121+
- `isAttachmentEqualHandler.test.js`, `MessageContent.test.js`, `MessageTextContainer.test.tsx`, `MessageUserReactions.test.tsx`, `ChannelPreview.test.tsx` β€” various pre-existing issues
146122

147-
- `components/Message/MessageItemView/MessageItemView.tsx` β€” 15+ component keys
148-
- `components/Message/MessageItemView/MessageContent.tsx` β€” 15+ component keys
149-
- `components/MessageList/MessageList.tsx` β€” 10+ component keys
150-
- `components/MessageList/MessageFlashList.tsx` β€” 10+ component keys
151-
- `components/MessageInput/MessageComposer.tsx` β€” 25+ component keys
152-
- `components/Attachment/Attachment.tsx` β€” 10+ component keys
153-
- `components/ChannelList/ChannelListView.tsx` β€” multiple component keys
154-
- `components/ChannelPreview/ChannelPreviewView.tsx` β€” multiple component keys
123+
### Linter Interaction
155124

156-
Many other files destructure just 1-2 component keys from context β€” straightforward replacements.
125+
`@typescript-eslint/no-unused-vars` (warn, max-warnings 0) aggressively strips unused type keys. When adding new keys to `ComponentOverrides`, the type and its consumer must land in the same edit β€” otherwise the linter removes the key between saves.
157126

158-
### Step 7: Update exports
127+
Since `ComponentOverrides` is now auto-derived from `DEFAULT_COMPONENTS`, this is no longer an issue for the type itself. But be aware when adding optional components (`undefined as React.ComponentType<any> | undefined`).
159128

160-
- `package/src/contexts/index.ts` β€” add `export * from './componentsContext/ComponentsContext'`
161-
- `package/src/index.ts` β€” verify `WithComponents`, `ComponentOverrides`, `useComponentsContext` are exported
129+
### `contexts/index.ts` Barrel Export
162130

163-
### Step 8: Update tests
131+
The `export * from './componentsContext/ComponentsContext'` line in `contexts/index.ts` was stripped by the linter multiple times during development. If `WithComponents` becomes unexportable from the package, check this barrel file first.
164132

165-
Tests that pass component overrides as Channel/ChannelList props will need to wrap in `<WithComponents>` instead. Mock builders that set up context values with component overrides may also need updating.
133+
### Documentation
166134

167-
## Edge Cases
135+
Docs PR: https://github.com/GetStream/docs-content/pull/1169
168136

169-
- **Shared names**: `EmptyStateIndicator`, `LoadingIndicator` exist in both Channel and ChannelList. One key in flat map β€” users use nesting for different overrides per area.
170-
- **Mixed destructuring**: Some consumers destructure both components and runtime data from the same `useMessagesContext()` call. These need to be split into two calls.
171-
- **`FlatList`**: Runtime-resolved from NativeHandlers. Stays in MessagesContext as a runtime value, not in ComponentsContext.
172-
- **`StopMessageStreamingButton`**: Supports `null` to hide. ComponentOverrides type allows `| null`.
137+
Updated ~45 pages across:
138+
- Core teaching pages (custom_components, message-customization, etc.)
139+
- Component reference pages (channel-list, message-list, message-composer, etc.)
140+
- Context docs (stripped component keys from 7 context pages)
141+
- Migration guide (upgrading-from-v8.md β€” comprehensive WithComponents section)
142+
- Advanced guides (audio, AI, image-picker, etc.)
173143

174-
## Verification
144+
### SDK PR
175145

176-
1. `cd package && yarn build` β€” type-checks and builds
177-
2. `cd package && yarn test:unit` β€” tests pass (after updating test fixtures)
178-
3. `cd package && yarn lint` β€” no lint errors
179-
4. Manual: `<WithComponents value={{ Message: Custom }}>` β†’ verify override appears
180-
5. Verify nesting: inner `WithComponents` wins over outer
146+
https://github.com/GetStream/stream-chat-react-native/pull/3542

0 commit comments

Comments
Β (0)