Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
stream-ci-bot committed Sep 27, 2024
1 parent ff4d398 commit fe6ad29
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 83 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
144 changes: 61 additions & 83 deletions docusaurus/docs/reactnative/guides/custom-thread-list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,16 @@ In this cookbook we'll go over how we can create and customize a screen containi

This cookbook assumes that you've already set up a separate screen that is able to display a single [`Thread`](../ui-components/thread.mdx) along with all of its replies. If this is indeed the case, there are no changes that you will need to do in order to have it working for the `ThreadList` screen.

It also assumes that the thread reply capability is enabled within the app and that you have a functioning `chatClient` that is used elsewhere.
It also assumes that you have a functioning `chatClient` that is used elsewhere.

For illustration purposes, we are going to be using the `React Navigation` library for navigation (however, any navigation library can be used).

### Creating the screen

Creating the `ThreadList` screen is simple. All you need to do is invoke the [`ThreadList` component](../ui-components/thread-list.mdx) inside of the `Chat` one, like so:
Here is how you add the [`ThreadList` component](../ui-components/thread-list.mdx) in a new screen example:

```tsx
import { OverlayProvider, Chat, ThreadList } from 'stream-chat-react-native';
// a basic chat client
import { client } from 'chat-client.tsx';

const ThreadListScreen = () => {
return (
Expand All @@ -37,20 +35,24 @@ const ThreadListScreen = () => {

This alone should be enough to already display all of the threads the user is involved in using the standard UI.

![Step 1 Preview](../assets/guides/custom-thread-list/custom-thread-list-step-1.png)

As a next step, let's make sure that the `ThreadList` updates internally only when the screen is in focus. For this purpose, we can use the `isFocused` property like so:

```tsx
import { OverlayProvider, Chat, ThreadList } from 'stream-chat-react-native';
// a basic chat client
import { client } from 'chat-client.tsx';
// highlight-start
// any navigation library hook/method can be used for this
import { useIsFocused } from '@react-navigation/native';
// highlight-end

const ThreadListScreen = () => {
// highlight-next-line
const isFocused = useIsFocused();
return (
<OverlayProvider>
<Chat client={client}>
// highlight-next-line
<ThreadList isFocused={isFocused} />
</Chat>
</OverlayProvider>
Expand All @@ -64,64 +66,77 @@ The `ThreadList` should display as intended now and refresh only when we're look

```tsx
import { OverlayProvider, Chat, ThreadList } from 'stream-chat-react-native';
// a basic chat client
import { client } from 'chat-client.tsx';
// any navigation library hook/method can be used for this
// highlight-next-line
import { useNavigation, useIsFocused } from '@react-navigation/native';

const ThreadListScreen = () => {
const isFocused = useIsFocused();
// highlight-next-line
const navigation = useNavigation();
return (
<OverlayProvider>
<Chat client={client}>
<ThreadList
isFocused={isFocused}
{/* here we can reuse the same method as we would in the ChannelList component */}
// highlight-start
// here we can reuse the same method as we would in the ChannelList component
onThreadSelect={(thread, channel) => {
navigation.navigate('ThreadScreen', {
thread,
channel,
});
}}
// highlight-end
/>
</Chat>
</OverlayProvider>
);
};
```

Now we should be able to navigate to any thread displayed in the list without an issue. For the next step, let's assume that we do not need all of the information displayed in each item within the `ThreadList` - but instead are really only interested in displaying the ID of each thread. We also want these to be separated by exactly 10 pixels apart.
Now we should be able to navigate to any thread displayed in the list. For the next step, let's assume that we do not need all of the information displayed in each item within the `ThreadList` - but instead are really only interested in displaying the ID of each thread. We also want these to be separated by exactly 10 pixels apart.

For this, we can override how the `ThreadListItem` is rendered within the list.

```tsx
import { TouchableOpacity, Text } from 'react-native';
import { OverlayProvider, Chat, ThreadList, useThreadsContext, useThreadListItemContext } from 'stream-chat-react-native';
// a basic chat client
import { client } from 'chat-client.tsx';
// highlight-start
import {
OverlayProvider,
Chat,
ThreadList,
useThreadsContext,
useThreadListItemContext,
MessageType,
} from 'stream-chat-react-native';
// highlight-end

// any navigation library hook/method can be used for this
import { useNavigation, useIsFocused } from '@react-navigation/native';

// highlight-start
const ThreadListItem = () => {
// we grab the definition of the navigation function from the ThreadsContext
const { onThreadSelect } = useThreadsContext();
// we grab the actual thread, channel and its parent message from the ThreadListItemContext
const { channel, thread, parentMessage } = useThreadListItemContext();
return (
<TouchableOpacity
style={{ backgroundColor: 'red', padding: 5 }}
onPress={() => {
if (onThreadSelect) {
// since we are overriding the behaviour of the item it is mandatory to pass the parameters in the
// below to onThreadSelect()
onThreadSelect({ thread: parentMessage, threadInstance: thread }, channel);
onThreadSelect({ thread: parentMessage as MessageType, threadInstance: thread }, channel);
}
}}
>
<Text>{thread?.id}</Text>
</TouchableOpacity>
)
}
// highlight-end

const ThreadListScreen = () => {
const isFocused = useIsFocused();
Expand All @@ -138,6 +153,7 @@ const ThreadListScreen = () => {
channel,
});
}}
// highlight-next-line
ThreadListItem={ThreadListItem}
/>
</Chat>
Expand All @@ -146,39 +162,34 @@ const ThreadListScreen = () => {
};
```

Now we should have successfully rendered the thread list items. As a second to last step, we would like to display a banner right on top of the list telling us how many unread threads we have in total just so that we can keep track.
Now we should have successfully rendered the thread list items.

![Step 2 Preview](../assets/guides/custom-thread-list/custom-thread-list-step-2.png)

For that, we can rely on using the state store our SDK exposes:
As a second to last step, we would like to display a banner right on top of the list telling us how many unread threads we have in total just so that we can keep track.

For that, we can rely on using the [state store](../state-and-offline-support/state-overview.mdx#thread-and-threadmanager) our SDK exposes and the [`useStateStore` hook](../state-and-offline-support/state-overview.mdx#usestatestore-hook):

```tsx
import { TouchableOpacity, Text, View } from 'react-native';
import { OverlayProvider, Chat, ThreadList, useThreadsContext, useThreadListItemContext, useStateStore } from 'stream-chat-react-native';
import {
OverlayProvider,
Chat,
ThreadList,
useThreadsContext,
useThreadListItemContext,
MessageType,
// highlight-next-line
useStateStore,
} from 'stream-chat-react-native';
// highlight-next-line
import { ThreadManagerState } from 'stream-chat';
// a basic chat client
import { client } from 'chat-client.tsx';
// any navigation library hook/method can be used for this
import { useNavigation, useIsFocused } from '@react-navigation/native';

const ThreadListItem = () => {
// we grab the definition of the navigation function from the ThreadsContext
const { onThreadSelect } = useThreadsContext();
// we grab the actual thread, channel and its parent message from the ThreadListItemContext
const { channel, thread, parentMessage } = useThreadListItemContext();
return (
<TouchableOpacity
onPress={() => {
if (onThreadSelect) {
// since we are overriding the behaviour of the item it is mandatory to pass the parameters in the
// below to onThreadSelect()
onThreadSelect({ thread: parentMessage, threadInstance: thread }, channel);
}
}}
>
<Text>{thread?.id}</Text>
</TouchableOpacity>
)
}
// ...

// highlight-start
// create a selector for unreadThreadCount
const selector = (nextValue: ThreadManagerState) => [nextValue.unreadThreadCount];

Expand All @@ -188,20 +199,23 @@ const CustomBanner = () => {

// display the banner
return (
<View style={{ flex: 1, paddingVertical: 15, paddingHorizontal: 5 }}>
<View style={{ paddingVertical: 15, paddingHorizontal: 5 }}>
<Text>You have {unreadCount} unread threads !</Text>
</View>
);
};
// highlight-end

const ThreadListScreen = () => {
const isFocused = useIsFocused();
const navigation = useNavigation();
return (
<OverlayProvider>
<Chat client={client}>
// highlight-start
{/* it's important that the banner is also a child of <Chat /> */}
<CustomBanner />
// highlight-end
<ThreadList
isFocused={isFocused}
{/* here we can reuse the same method as we would in the ChannelList component */}
Expand All @@ -219,56 +233,16 @@ const ThreadListScreen = () => {
};
```

You may find more information on how `useStateStore` works [here](../state-and-offline-support/state-overview.mdx#thread-and-threadmanager).
![Step 3 Preview](../assets/guides/custom-thread-list/custom-thread-list-step-3.png)

And as a final step, we would like each item in the `ThreadList` to be separated by 10 pixels. Since we do not have a specific property for this purpose, we can always rely on the `FlatList` properties that can be accessed through the `additionalFlatListProps` property.
And as a final step, we would like each item in the `ThreadList` to be separated by 10 pixels. For this, we can use the [`FlatList` properties](https://reactnative.dev/docs/flatlist#props) that can be accessed through the `additionalFlatListProps` prop.

Finally, our screen would look like this:

```tsx
import { TouchableOpacity, Text, View } from 'react-native';
import { OverlayProvider, Chat, ThreadList, useThreadsContext, useThreadListItemContext, useStateStore } from 'stream-chat-react-native';
import { ThreadManagerState } from 'stream-chat';
// a basic chat client
import { client } from 'chat-client.tsx';
// any navigation library hook/method can be used for this
import { useNavigation, useIsFocused } from '@react-navigation/native';

const ThreadListItem = () => {
// we grab the definition of the navigation function from the ThreadsContext
const { onThreadSelect } = useThreadsContext();
// we grab the actual thread, channel and its parent message from the ThreadListItemContext
const { channel, thread, parentMessage } = useThreadListItemContext();
return (
<TouchableOpacity
onPress={() => {
if (onThreadSelect) {
// since we are overriding the behaviour of the item it is mandatory to pass the parameters in the
// below to onThreadSelect()
onThreadSelect({ thread: parentMessage, threadInstance: thread }, channel);
}
}}
>
<Text>{thread?.id}</Text>
</TouchableOpacity>
)
}

// create a selector for unreadThreadCount
const selector = (nextValue: ThreadManagerState) => [nextValue.unreadThreadCount];

const CustomBanner = () => {
// use our utility hook to access the store
const [unreadCount] = useStateStore(client?.threads?.state, selector);

// display the banner
return (
<View style={{ flex: 1, paddingVertical: 15, paddingHorizontal: 5 }}>
<Text>You have {unreadCount} unread threads !</Text>
</View>
);
};
// ...

// highlight-next-line
const ItemSeparatorComponent = () => <View style={{ paddingVertical: 5 }} />

const ThreadListScreen = () => {
Expand All @@ -289,14 +263,18 @@ const ThreadListScreen = () => {
});
}}
ThreadListItem={ThreadListItem}
// highlight-start
additionalFlatListProps={{
ItemSeparatorComponent,
}}
// highlight-end
/>
</Chat>
</OverlayProvider>
);
};
```

![Step 4 Preview](../assets/guides/custom-thread-list/custom-thread-list-step-4.png)

We now have an entirely custom `ThreadList` that we can further modify to our liking.

0 comments on commit fe6ad29

Please sign in to comment.