diff --git a/native/chat/sidebar-list-modal.react.js b/native/chat/sidebar-list-modal.react.js
--- a/native/chat/sidebar-list-modal.react.js
+++ b/native/chat/sidebar-list-modal.react.js
@@ -1,7 +1,7 @@
 // @flow
 
 import * as React from 'react';
-import { TextInput, FlatList, View } from 'react-native';
+import { View } from 'react-native';
 
 import { useSearchSidebars } from 'lib/hooks/search-threads';
 import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types';
@@ -9,26 +9,16 @@
 import ExtendedArrow from '../components/arrow-extended.react';
 import Arrow from '../components/arrow.react';
 import Button from '../components/button.react';
-import Modal from '../components/modal.react';
-import Search from '../components/search.react';
 import type { RootNavigationProp } from '../navigation/root-navigator.react';
 import type { NavigationRoute } from '../navigation/route-names';
-import { useColors, useIndicatorStyle, useStyles } from '../themes/colors';
-import { waitForModalInputFocus } from '../utils/timers';
-import { useNavigateToThread } from './message-list-types';
+import { useColors, useStyles } from '../themes/colors';
 import { SidebarItem } from './sidebar-item.react';
+import ThreadListModal from './thread-list-modal.react';
 
 export type SidebarListModalParams = {
   +threadInfo: ThreadInfo,
 };
 
-function keyExtractor(sidebarInfo: SidebarInfo) {
-  return sidebarInfo.threadInfo.id;
-}
-function getItemLayout(data: ?$ReadOnlyArray<SidebarInfo>, index: number) {
-  return { length: 24, offset: 24 * index, index };
-}
-
 type Props = {
   +navigation: RootNavigationProp<'SidebarListModal'>,
   +route: NavigationRoute<'SidebarListModal'>,
@@ -41,45 +31,13 @@
     onChangeSearchInputText,
   } = useSearchSidebars(props.route.params.threadInfo);
 
-  const searchTextInputRef = React.useRef();
-  const setSearchTextInputRef = React.useCallback(
-    async (textInput: ?React.ElementRef<typeof TextInput>) => {
-      searchTextInputRef.current = textInput;
-      if (!textInput) {
-        return;
-      }
-      await waitForModalInputFocus();
-      if (searchTextInputRef.current) {
-        searchTextInputRef.current.focus();
-      }
-    },
-    [],
-  );
-
-  const navigateToThread = useNavigateToThread();
-  const onPressItem = React.useCallback(
-    (threadInfo: ThreadInfo) => {
-      setSearchState({
-        text: '',
-        results: new Set(),
-      });
-      if (searchTextInputRef.current) {
-        searchTextInputRef.current.blur();
-      }
-      navigateToThread({ threadInfo });
-    },
-    [navigateToThread, setSearchState],
-  );
-
-  const styles = useStyles(unboundStyles);
-
-  const numOfSidebarsWithExtendedArrow = React.useMemo(
-    () => listData.length - 1,
-    [listData],
-  );
+  const numOfSidebarsWithExtendedArrow = listData.length - 1;
 
-  const renderItem = React.useCallback(
-    (row: { item: SidebarInfo, index: number, ... }) => {
+  const createRenderItem = React.useCallback(
+    (
+      onPressItem: (threadInfo: ThreadInfo) => void,
+      // eslint-disable-next-line react/display-name
+    ) => (row: { +item: SidebarInfo, +index: number, ... }) => {
       let extendArrow: boolean = false;
       if (row.index < numOfSidebarsWithExtendedArrow) {
         extendArrow = true;
@@ -92,29 +50,19 @@
         />
       );
     },
-    [onPressItem, numOfSidebarsWithExtendedArrow],
+    [numOfSidebarsWithExtendedArrow],
   );
 
-  const indicatorStyle = useIndicatorStyle();
   return (
-    <Modal>
-      <Search
-        searchText={searchState.text}
-        onChangeText={onChangeSearchInputText}
-        containerStyle={styles.search}
-        placeholder="Search threads"
-        ref={setSearchTextInputRef}
-      />
-      <FlatList
-        data={listData}
-        renderItem={renderItem}
-        keyExtractor={keyExtractor}
-        getItemLayout={getItemLayout}
-        keyboardShouldPersistTaps="handled"
-        initialNumToRender={20}
-        indicatorStyle={indicatorStyle}
-      />
-    </Modal>
+    <ThreadListModal
+      createRenderItem={createRenderItem}
+      listData={listData}
+      searchState={searchState}
+      setSearchState={setSearchState}
+      onChangeSearchInputText={onChangeSearchInputText}
+      threadInfo={props.route.params.threadInfo}
+      searchPlaceholder="Search threads"
+    />
   );
 }
 
@@ -177,9 +125,6 @@
     position: 'absolute',
     top: -6,
   },
-  search: {
-    marginBottom: 8,
-  },
   sidebar: {
     backgroundColor: 'listBackground',
     paddingLeft: 0,
diff --git a/native/chat/thread-list-modal.react.js b/native/chat/thread-list-modal.react.js
new file mode 100644
--- /dev/null
+++ b/native/chat/thread-list-modal.react.js
@@ -0,0 +1,118 @@
+// @flow
+
+import * as React from 'react';
+import { TextInput, FlatList, StyleSheet } from 'react-native';
+
+import type { ThreadSearchState } from 'lib/hooks/search-threads';
+import type { ChatThreadItem } from 'lib/selectors/chat-selectors';
+import type { SetState } from 'lib/types/hook-types';
+import type { ThreadInfo, SidebarInfo } from 'lib/types/thread-types';
+
+import Modal from '../components/modal.react';
+import Search from '../components/search.react';
+import { useIndicatorStyle } from '../themes/colors';
+import { waitForModalInputFocus } from '../utils/timers';
+import { useNavigateToThread } from './message-list-types';
+
+function keyExtractor(sidebarInfo: SidebarInfo | ChatThreadItem) {
+  return sidebarInfo.threadInfo.id;
+}
+function getItemLayout(
+  data: ?$ReadOnlyArray<SidebarInfo | ChatThreadItem>,
+  index: number,
+) {
+  return { length: 24, offset: 24 * index, index };
+}
+
+type Props<U> = {
+  +threadInfo: ThreadInfo,
+  +createRenderItem: (
+    onPressItem: (threadInfo: ThreadInfo) => void,
+  ) => (row: {
+    +item: U,
+    +index: number,
+    ...
+  }) => React.Node,
+  +listData: $ReadOnlyArray<U>,
+  +searchState: ThreadSearchState,
+  +setSearchState: SetState<ThreadSearchState>,
+  +onChangeSearchInputText: (text: string) => mixed,
+  +searchPlaceholder: string,
+};
+function ThreadListModal<U: SidebarInfo | ChatThreadItem>(
+  props: Props<U>,
+): React.Node {
+  const {
+    searchState,
+    setSearchState,
+    onChangeSearchInputText,
+    listData,
+    createRenderItem,
+    searchPlaceholder,
+  } = props;
+
+  const searchTextInputRef = React.useRef();
+  const setSearchTextInputRef = React.useCallback(
+    async (textInput: ?React.ElementRef<typeof TextInput>) => {
+      searchTextInputRef.current = textInput;
+      if (!textInput) {
+        return;
+      }
+      await waitForModalInputFocus();
+      if (searchTextInputRef.current) {
+        searchTextInputRef.current.focus();
+      }
+    },
+    [],
+  );
+
+  const navigateToThread = useNavigateToThread();
+  const onPressItem = React.useCallback(
+    (threadInfo: ThreadInfo) => {
+      setSearchState({
+        text: '',
+        results: new Set(),
+      });
+      if (searchTextInputRef.current) {
+        searchTextInputRef.current.blur();
+      }
+      navigateToThread({ threadInfo });
+    },
+    [navigateToThread, setSearchState],
+  );
+
+  const renderItem = React.useMemo(() => createRenderItem(onPressItem), [
+    createRenderItem,
+    onPressItem,
+  ]);
+
+  const indicatorStyle = useIndicatorStyle();
+  return (
+    <Modal>
+      <Search
+        searchText={searchState.text}
+        onChangeText={onChangeSearchInputText}
+        containerStyle={styles.search}
+        placeholder={searchPlaceholder}
+        ref={setSearchTextInputRef}
+      />
+      <FlatList
+        data={listData}
+        renderItem={renderItem}
+        keyExtractor={keyExtractor}
+        getItemLayout={getItemLayout}
+        keyboardShouldPersistTaps="handled"
+        initialNumToRender={20}
+        indicatorStyle={indicatorStyle}
+      />
+    </Modal>
+  );
+}
+
+const styles = StyleSheet.create({
+  search: {
+    marginBottom: 8,
+  },
+});
+
+export default ThreadListModal;