# Custom Thread List

This cookbook shows how to build a custom screen with [`ThreadList`](https://getstream.io/chat/docs/sdk/react-native/ui-components/thread-list/) and a banner for unread threads.

## Best Practices

- Keep `ThreadList` within `Chat` so it has access to client state and contexts.
- Use `isFocused` to avoid unnecessary updates when the screen is not visible.
- Reuse `onThreadSelect` so thread navigation stays consistent across the app.
- Keep custom `ThreadListItem` lightweight to maintain scroll performance.
- Pull unread counts from the state store to avoid extra client queries.

## Prerequisites

A screen that shows a [`Thread`](https://getstream.io/chat/docs/sdk/react-native/ui-components/thread/) and a working `chatClient` are required. Examples use React Navigation, but any navigation library works.

## Creating the Screen

Add [`ThreadList`](https://getstream.io/chat/docs/sdk/react-native/ui-components/thread-list/) to a new screen:

```tsx
import { OverlayProvider, Chat, ThreadList } from "stream-chat-react-native";

const ThreadListScreen = () => {
  return (
    <OverlayProvider>
      <Chat client={client}>
        <ThreadList />
      </Chat>
    </OverlayProvider>
  );
};
```

This renders the user's threads with the default UI.

![Default thread list screen](https://getstream.io/docs-assets/images/59dffa780407.png)

## Optimizing with Focus State

Update the list only when the screen is focused using `isFocused`. This is useful when `ThreadList` lives in a tab that stays mounted:

```tsx {2,3,6,10}
import { OverlayProvider, Chat, ThreadList } from "stream-chat-react-native";
// any navigation library hook/method can be used for this
import { useIsFocused } from "@react-navigation/native";

const ThreadListScreen = () => {
  const isFocused = useIsFocused();
  return (
    <OverlayProvider>
      <Chat client={client}>
        <ThreadList isFocused={isFocused} />
      </Chat>
    </OverlayProvider>
  );
};
```

## Handling Thread Navigation

Now the list refreshes only when focused. Next, handle item taps and navigate to the thread:

```tsx {3,7,13,14,15,16,17,18,19}
import { OverlayProvider, Chat, ThreadList } from "stream-chat-react-native";
// any navigation library hook/method can be used for this
import { useNavigation, useIsFocused } from "@react-navigation/native";

const ThreadListScreen = () => {
  const isFocused = useIsFocused();
  const navigation = useNavigation();
  return (
    <OverlayProvider>
      <Chat client={client}>
        <ThreadList
          isFocused={isFocused}
          onThreadSelect={(thread, channel) => {
            navigation.navigate("ThreadScreen", {
              thread,
              channel,
            });
          }}
        />
      </Chat>
    </OverlayProvider>
  );
};
```

## Overriding ThreadListItem

Override `ThreadListItem` to render a custom layout per item. This example shows only the thread ID:

```tsx {2,3,4,5,6,7,8,9,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,50}
import { TouchableOpacity, Text } from "react-native";
import type { LocalMessage } from "stream-chat";
import {
  OverlayProvider,
  Chat,
  ThreadList,
  useThreadsContext,
  useThreadListItemContext,
} from "stream-chat-react-native";

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

const ThreadListItem = () => {
  const { onThreadSelect } = useThreadsContext();
  const { channel, thread, parentMessage } = useThreadListItemContext();
  return (
    <TouchableOpacity
      style={{ backgroundColor: "red", padding: 5 }}
      onPress={() => {
        if (onThreadSelect) {
          onThreadSelect(
            { thread: parentMessage as LocalMessage, threadInstance: thread },
            channel,
          );
        }
      }}
    >
      <Text>{thread?.id}</Text>
    </TouchableOpacity>
  );
};

const ThreadListScreen = () => {
  const isFocused = useIsFocused();
  const navigation = useNavigation();
  return (
    <OverlayProvider>
      <Chat client={client}>
        <ThreadList
          isFocused={isFocused}
          onThreadSelect={(thread, channel) => {
            navigation.navigate("ThreadScreen", {
              thread,
              channel,
            });
          }}
          ThreadListItem={ThreadListItem}
        />
      </Chat>
    </OverlayProvider>
  );
};
```

![Thread list with custom items showing thread IDs](https://getstream.io/docs-assets/images/f387d8321753.png)

When overriding the item, pass the required parameters to `onThreadSelect()` so navigation continues to work.

## Adding an Unread Threads Banner

Add a banner above the list that shows the unread thread count. Use the [state store](https://getstream.io/chat/docs/sdk/react-native/client/state-overview#thread-and-threadmanager) and [`useStateStore`](https://getstream.io/chat/docs/sdk/react-native/client/state-overview#usestatestore-hook) to read the count reactively:

```tsx {9,11,17,18,19,20,21,22,23,24,25,26,27,28,29,30,38,39}
import { TouchableOpacity, Text, View } from "react-native";
import {
  OverlayProvider,
  Chat,
  ThreadList,
  useThreadsContext,
  useThreadListItemContext,
  useStateStore,
} from "stream-chat-react-native";
import type { LocalMessage } from "stream-chat";
import { ThreadManagerState } from "stream-chat";
// any navigation library hook/method can be used for this
import { useNavigation, useIsFocused } from "@react-navigation/native";

// ...

const selector = (nextValue: ThreadManagerState) => [
  nextValue.unreadThreadCount,
];

const CustomBanner = () => {
  const [unreadCount] = useStateStore(client?.threads?.state, selector);

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

const ThreadListScreen = () => {
  const isFocused = useIsFocused();
  const navigation = useNavigation();
  return (
    <OverlayProvider>
      <Chat client={client}>
        {/* The banner must be a child of <Chat /> to access client state */}
        <CustomBanner />
        <ThreadList
          isFocused={isFocused}
          onThreadSelect={(thread, channel) => {
            navigation.navigate("ThreadScreen", {
              thread,
              channel,
            });
          }}
          ThreadListItem={ThreadListItem}
        />
      </Chat>
    </OverlayProvider>
  );
};
```

![Thread list with unread count banner](https://getstream.io/docs-assets/images/e98b20201654.png)

## Customizing List Spacing

Add spacing between items using `FlatList` props via `additionalFlatListProps`:

```tsx {3,23,24,25}
// ...

const ItemSeparatorComponent = () => <View style={{ paddingVertical: 5 }} />;

const ThreadListScreen = () => {
  const isFocused = useIsFocused();
  const navigation = useNavigation();
  return (
    <OverlayProvider>
      <Chat client={client}>
        <CustomBanner />
        <ThreadList
          isFocused={isFocused}
          onThreadSelect={(thread, channel) => {
            navigation.navigate("ThreadScreen", {
              thread,
              channel,
            });
          }}
          ThreadListItem={ThreadListItem}
          additionalFlatListProps={{
            ItemSeparatorComponent,
          }}
        />
      </Chat>
    </OverlayProvider>
  );
};
```

![Thread list with item spacing](https://getstream.io/docs-assets/images/1196c58ff2cb.png)

You now have a fully customized `ThreadList`.


---

This page was last updated at 2026-08-17T13:13:20.614Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/guides/custom-thread-list/](https://getstream.io/chat/docs/sdk/react-native/guides/custom-thread-list/).