Skip to content

Commit

Permalink
Merge pull request #2697 from GetStream/develop
Browse files Browse the repository at this point in the history
Next Release
  • Loading branch information
isekovanic authored Oct 2, 2024
2 parents 2b1a426 + 4e2596b commit a3a6933
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 123 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[![NPM](https://img.shields.io/npm/v/stream-chat-react-native.svg)](https://www.npmjs.com/package/stream-chat-react-native)
[![Build Status](https://github.com/GetStream/stream-chat-react-native/actions/workflows/release.yml/badge.svg)](https://github.com/GetStream/stream-chat-react-native/actions)
[![Component Reference](https://img.shields.io/badge/docs-component%20reference-blue.svg)](https://getstream.io/chat/docs/sdk/reactnative)
![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-442%20KB-blue)
![JS Bundle Size](https://img.shields.io/badge/js_bundle_size-447%20KB-blue)

<img align="right" src="https://getstream.imgix.net/images/ios-chat-tutorial/[email protected]?auto=format,enhance" width="50%" />

Expand Down
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.
27 changes: 16 additions & 11 deletions examples/ExpoMessaging/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7310,10 +7310,10 @@ [email protected], stream-buffers@~2.2.0:
version "0.0.0"
uid ""

stream-chat-react-native-core@5.38.0:
version "5.38.0"
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.38.0.tgz#3d394a8ea9ab8d9a06a47000ff90a44daae35aaa"
integrity sha512-l9wF3Y+caFL1J2urw+Od0U2JNkW4ff/o4rlpmIOe92Ow14kuQOrejKo1XJbC6riJBVpnrtT1tj8SfVeS2crW0Q==
stream-chat-react-native-core@5.39.0:
version "5.39.0"
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.39.0.tgz#499db88ff9a6ef2f93012f2dd1c0feb3c849859c"
integrity sha512-dzA/N3pOWMmRtNmMWYfnOnGSBG+K+ReKqLL3CXYmSnUmEHQlJwHqKDLPlxnNY8IOT2Nh067m04aB+1OQZpi/Mw==
dependencies:
"@gorhom/bottom-sheet" "^4.6.4"
dayjs "1.10.5"
Expand All @@ -7326,16 +7326,16 @@ [email protected]:
path "0.12.7"
react-native-markdown-package "1.8.2"
react-native-url-polyfill "^1.3.0"
stream-chat "8.39.0"
stream-chat "8.40.8"

"stream-chat-react-native-core@link:../../package":
version "0.0.0"
uid ""

stream-chat@8.39.0:
version "8.39.0"
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.39.0.tgz#f4cb86bd5cac4c1272c24cd66ed4752bcda8d717"
integrity sha512-zQZR1tPrgGBbu+Gnv9F9KQx3OPUMvb0FN+39BEjkjgjRPm2JYhF78jfcYutQMiC538t3V+NgFGgj5N4sZvSsUA==
stream-chat@8.40.8:
version "8.40.8"
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.40.8.tgz#0f5320bd8b03d1cbff377f8c7ae2f8afe24d0515"
integrity sha512-nYLvYAkrvXRzuPO52TIofNiInCkDdXrnBc/658297lC6hzrHNc87mmTht264BXmXLlpasTNP3rLKxR6MxhpgKg==
dependencies:
"@babel/runtime" "^7.16.3"
"@types/jsonwebtoken" "~9.0.0"
Expand All @@ -7345,7 +7345,7 @@ [email protected]:
form-data "^4.0.0"
isomorphic-ws "^4.0.1"
jsonwebtoken "~9.0.0"
ws "^7.4.4"
ws "^7.5.10"

stream-slice@^0.1.2:
version "0.1.2"
Expand Down Expand Up @@ -8074,11 +8074,16 @@ ws@^6.2.2:
dependencies:
async-limiter "~1.0.0"

ws@^7, ws@^7.4.4, ws@^7.5.1:
ws@^7, ws@^7.5.1:
version "7.5.9"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==

ws@^7.5.10:
version "7.5.10"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==

ws@^8.12.1:
version "8.13.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
Expand Down
Loading

0 comments on commit a3a6933

Please sign in to comment.