Skip to content

Commit 941707d

Browse files
feat: add ThreadList and ThreadProvider (Threads 2.0) (#2407)
🚂 GetStream/stream-chat-js#1330 This PR adds components for implementing a thread list view: ThreadList and ChatView. It also adds support for reactive state stores, like the ones that handle the thread list logic in the client. Implements a binding from the reactive state store in the client to React components. Implements a thread list. The component itself consists mostly of binding to the `ThreadManager` instance in the client. The Thread component was modified to optionally use a reactive `Thread` instance from the `ThreadContext`. The component checks if `ThreadContext` is provided, and if so uses the thread instance from that context. Otherwise, it falls back to the `ChannelContext` as usual. Currently, we use `ThreadContext` in the thread list view, where it is provided by the `ChatView.ThreadAdapter` component. Note that even when `ThreadContext` is used, `ChannelContext` also must be provided. Most our components, including the ones used in threads, still expect the channel context to be there. A set of components implementing a simple in-memory router to switch between channel view and thread list view. Using it is totally optional. Most integrations will probably use their own router instead. Still, nice to have something out-of-the-box. A new way to provide component overrides. Previously we allowed passing component overrides as props to `Channel` component, and then in some nested components as well, although not consistently. `WithComponents` is a new recommended way to provide component overrides, which requires far less prop drilling and nests nicely. BREAKING CHANGE: ComponentContext no longer provides any defaults --------- Co-authored-by: Matvei Andrienko <[email protected]>
1 parent a39ceda commit 941707d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+3161
-279
lines changed

docusaurus/docs/React/components/contexts/component-context.mdx

+29
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,35 @@ Pull values from context with our custom hook:
1818
const { Attachment, Avatar, Message } = useComponentContext();
1919
```
2020

21+
## WithComponents
22+
23+
A component override functionality which utilises `ComponentContext` under the hood. This is direct replacement for a prop-based component overrides which are now slowly being deprecated.
24+
25+
### Basic Usage of WithComponents
26+
27+
In this case, top-level [`MessageInput`](../message-input-components/message-input.mdx) component reaches for the closest overrides and applies `MessageInputUi1`, the [`Thread`](../core-components/thread.mdx) component uses [`MessageInput`](../message-input-components/message-input.mdx) internally and its UI can be also overriden - in this case, the closest one provides override with component `MessageInputUi2`. If we were to remove this `WithComponents` wrapper over [`Thread`](../core-components/thread.mdx) component, the closest override for [`Thread`](../core-components/thread.mdx)'s [`MessageInput`](../message-input-components/message-input.mdx) component would be `MessageInputUi1`.
28+
29+
```tsx
30+
const MessageInputUi1 = () => {
31+
/*...*/
32+
};
33+
const MessageInputUi2 = () => {
34+
/*...*/
35+
};
36+
37+
<Channel>
38+
<WithComponents overrides={{ Input: MessageInputUi1 }}>
39+
<Window>
40+
<MessageList />
41+
<MessageInput focus />
42+
</Window>
43+
<WithComponents overrides={{ Input: MessageInputUi2 }}>
44+
<Thread />
45+
</WithComponents>
46+
</WithComponents>
47+
</Channel>;
48+
```
49+
2150
## Values
2251

2352
### Attachment
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
id: thread-context
3+
title: ThreadContext
4+
---
5+
6+
`ThreadContext` - just like any other React context - is used for dependency injection. What makes it different in this case is `ThreadProvider`.
7+
8+
## ThreadProvider
9+
10+
Is a provider which wraps [`Channel`](../core-components/channel.mdx) component and takes [`Thread` instance]() as a value. The [`Channel`](../core-components/channel.mdx) wrapper acts as a temporary measure to make [`Thread` component](../core-components/thread.mdx) compatible with the new architecture which relies on [`Thread` instance](). The reliance on channel is temporary and will become deprecated in the future.
11+
12+
Thread component newly prioritizes [`Thread` instance]() if rendered under [`ThreadProvider`](../contexts/thread-context.mdx#threadprovider) otherwise falls back to accessing thread from [`Channel` state](../contexts/channel-state-context.mdx).
13+
14+
### Basic Usage
15+
16+
```tsx
17+
import { Thread, ThreadProvider } from 'stream-chat-react';
18+
19+
<ThreadProvider thread={/*...*/}>
20+
<Thread />
21+
</ThreadProvider>;
22+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
id: thread-list-item
3+
title: ThreadListItem
4+
---
5+
6+
An item component rendered within [`ThreadList` component](./thread-list.mdx). The item is divided into two components:
7+
8+
`ThreadListItem` - a component and provider which renders `ThreadListItemUi`
9+
`ThreadListItemUi` - a component which renders the actual UI elements
10+
11+
The goal is that as integrator you can provide a different look to your component while preserving the behavior or you can replace the behavior while keeping the default UI or you can change both if you require so.
12+
13+
## Props
14+
15+
### thread
16+
17+
A thread instance provided by the [`ThreadList`](../core-components/thread-list.mdx).
18+
19+
| Type |
20+
| ------ |
21+
| Thread |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
id: thread-list
3+
title: ThreadList
4+
---
5+
6+
`ThreadList` is a component which renders individual thread instances ([`Thread`](https://github.com/GetStream/stream-chat-js/blob/master/src/thread.ts)) stored within `ThreadManager`. It handles pagination triggers and virtualization through the help of the [Virtuoso](https://virtuoso.dev) virtualized list component. The rest of the business logic lives within ThreadManager and Thread classes. ThreadManager instance gets activated whenever ThreadList renders - activation is necessary as it tells the SDK that user "sees" this list and can update state accordingly whenever appropriate events arrive.
7+
8+
If used in default form and rendered within `ThreadView` component it also allows to set active thread and handles `Thread` activation (similar to `ThreadManager` activation).
9+
10+
## Basic Usage
11+
12+
```tsx
13+
<Chat client={client}>
14+
{/*...*/}
15+
<ThreadList />
16+
</Chat>
17+
```
18+
19+
## Props
20+
21+
### virtuosoProps
22+
23+
Props to be passed to the underlying [`react-virtuoso` virtualized list dependency](https://virtuoso.dev/virtuoso-api/interfaces/VirtuosoProps).
24+
25+
| Type |
26+
| ------ |
27+
| object |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
id: chat-view
3+
title: ChatView
4+
keywords: [example, chat view, channel view, thread view, thread adapter]
5+
---
6+
7+
`ChatView` is component itself and a set of components which allow for a drop-in implementation of different chat views - the channel view and thread view. This drop-in solution allows your users to easily switch between said views without having to implement such mechanism yourself. It consists of:
8+
9+
- `ChatView` - a provider that holds information about the selected view
10+
- `ChatView.Selector` - selector which allows to set the required view
11+
- `ChatView.Channels` - a wrapper that renders its children when `ChatView` value is equal to `channels`
12+
- `ChatView.Threads` - a provider and a wrapper that renders its children when `ChatView` value is equal to `threads`, exposes `ThreadsViewContext` under which `ThreadList` can set an active thread
13+
- `ChatView.ThreadAdapter` - a wrapper which can access an active thread from the `ThreadsViewContext` and forwards it to the [`ThreadProvider`](../contexts/thread-context.mdx)
14+
15+
## Basic Usage
16+
17+
```tsx
18+
import {
19+
Chat,
20+
ChatView,
21+
ChannelList,
22+
Channel,
23+
ThreadList,
24+
Thread,
25+
useCreateChatClient,
26+
} from 'stream-chat-react';
27+
28+
const App = () => {
29+
const chatClient = useCreateChatClient(/*...*/);
30+
31+
if (!chatClient) return null;
32+
33+
return (
34+
<Chat client={chatClient}>
35+
<ChatView>
36+
<ChatView.Selector />
37+
{/* Channel View */}
38+
<ChatView.Channels>
39+
<ChannelList />
40+
<Channel>{/*...*/}</Channel>
41+
</ChatView.Channels>
42+
{/* Thread View */}
43+
<ChatView.Threads>
44+
<ThreadList />
45+
<ChatView.ThreadAdapter>
46+
<Thread />
47+
</ChatView.ThreadAdapter>
48+
</ChatView.Threads>
49+
</ChatView>
50+
</Chat>
51+
);
52+
};
53+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
id: view-components-and-thread-adapter
3+
title: View Components and Thread Adapter
4+
---
5+
6+
ChatView set of components is a view switching mechanism that can be utilised by integrators to quickly implement switching between thread and channel views.
7+
8+
Available components:
9+
10+
- `ChatView` - wrapper with context holding the information about currently active view (`channels` & `threads`)
11+
- `ChatView.Threads` - view used for thread-focused application structure with `ThreadsViewContext` that _can be_ utilised by `ThreadList` to set active thread
12+
- `ChatView.Channels` - view used for channel-focused application structure
13+
- `ChatView.Selector` - list with buttons with bound actions for switching views
14+
- `ChatView.ThreadAdapter` - utilises `ThreadsViewContext` and wraps `Thread` component in necessary `ThreadProvider`
15+
16+
This set of components is provided as-is and offers very limited customizability as the underlying logic is super simple. Integrators are encouraged to build their own switching system if they require it.
17+
18+
### Usage
19+
20+
```tsx
21+
import {
22+
Chat,
23+
ChatView,
24+
ChannelList,
25+
Channel,
26+
ThreadList,
27+
Thread,
28+
Window,
29+
} from 'stream-chat-react';
30+
31+
// application structure which allows users to switch between views
32+
<Chat client={client}>
33+
<ChatView>
34+
<ChatView.Selector />
35+
{/* channel-focused structure */}
36+
<ChatView.Channels>
37+
<ChannelList filters={filters} options={options} sort={sort} />
38+
<Channel>
39+
<Window>
40+
<ChannelHeader />
41+
<MessageList />
42+
<MessageInput focus />
43+
</Window>
44+
<Thread />
45+
</Channel>
46+
</ChatView.Channels>
47+
{/* thread-focused structure */}
48+
<ChatView.Threads>
49+
<ThreadList />
50+
<ChatView.ThreadAdapter>
51+
<Thread />
52+
</ChatView.ThreadAdapter>
53+
</ChatView.Threads>
54+
</ChatView>
55+
</Chat>;
56+
```
57+
58+
### Custom Thread-focused Structure
59+
60+
To build your custom thread-focused structure you'll need these four baseline components; `Thread`, `ThreadList`, `ThreadProvier` and `WithComponents` for component overrides.
61+
62+
:::note
63+
For presentation purposes our custom `ThreadListItemUi` component is loosely defined within `CustomThreadsView` and thus it isn't stable. To achieve best performance make sure your components
64+
are stable and defined outside other component's scope.
65+
:::
66+
67+
```tsx
68+
import { WithComponents, ThreadListItemUi, ThreadList, ThreadProvider } from 'stream-chat-react';
69+
70+
export const CustomThreadsView = () => {
71+
const [activeThread, setActiveThread] = useState(undefined);
72+
73+
return (
74+
<div className='custom-threads-view'>
75+
<WithComponents
76+
overrides={{
77+
ThreadListItemUi: () => {
78+
const thread = useThreadListItemContext()!;
79+
return (
80+
<ThreadListItemUi
81+
onPointerDown={() => setActiveThread(thread)}
82+
aria-selected={thread === activeThread}
83+
/>
84+
);
85+
},
86+
}}
87+
>
88+
<ThreadList />
89+
</WithComponents>
90+
91+
<ThreadProvider thread={activeThread}>
92+
<Thread />
93+
</ThreadProvider>
94+
</div>
95+
);
96+
};
97+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
id: custom-threads-view
3+
title: Custom Threads View
4+
keywords: [cookbook, threads, view]
5+
---
6+
7+
Our SDK comes with [`ChatView`](../components/utility-components/chat-view.mdx) which allows for an easy integration of different views. In this guide we'll show how to implement custom threads view while utilising core components and hooks.
8+
9+
## Required Components & Hooks
10+
11+
These components and hooks are required for your own implementation to work properly:
12+
13+
- `ThreadList`
14+
- `ThreadListItem` - a provider for `ThreadListItemUi` with thread information, will be used to forward custom click event to the `ThreadListItemUi` button
15+
- `ThreadProvider` - "adapter" for Thread component to work properly with [`Thread` instance](https://github.com/GetStream/stream-chat-js/blob/master/src/thread.ts)
16+
- `Thread` - provides [`MessageList`](../components/core-components/message-list.mdx) with [`MessageInput`](../components/message-input-components/message-input.mdx) adjusted for threads
17+
- `useActiveThread` - takes your selected thread instance and handles its activity state (`Thread.activate()` & `Thread.deactivate()`) based on document focus and visibility
18+
19+
## Building Custom Threads View
20+
21+
```tsx
22+
import {
23+
ThreadList,
24+
ThreadListItem,
25+
ThreadProvider,
26+
Thread,
27+
WithComponents,
28+
useActiveThread,
29+
} from 'stream-chat-react';
30+
31+
export const CustomThreadsView = () => {
32+
const [activeThread, setActiveThread] = useState(undefined);
33+
34+
useActiveThread({ activeThread });
35+
36+
return (
37+
<div className='custom-threads-view'>
38+
<ThreadList
39+
virtuosoProps={{
40+
itemContent: (_, thread) => (
41+
<ThreadListItem
42+
thread={thread}
43+
threadListItemUiProps={{
44+
'aria-selected': thread === activeThread,
45+
onClick: () => {
46+
setActiveThread(thread);
47+
},
48+
}}
49+
/>
50+
),
51+
}}
52+
/>
53+
54+
{activeThread && (
55+
<ThreadProvider thread={activeThread}>
56+
<Thread />
57+
</ThreadProvider>
58+
)}
59+
</div>
60+
);
61+
};
62+
```

0 commit comments

Comments
 (0)