diff --git a/web/avatars/web-edit-thread-avatar-provider.react.js b/web/avatars/web-edit-thread-avatar-provider.react.js
index 8ef56839c..060559781 100644
--- a/web/avatars/web-edit-thread-avatar-provider.react.js
+++ b/web/avatars/web-edit-thread-avatar-provider.react.js
@@ -1,25 +1,24 @@
// @flow
import * as React from 'react';
import { BaseEditThreadAvatarProvider } from 'lib/components/base-edit-thread-avatar-provider.react.js';
-import { useSelector } from '../redux/redux-utils.js';
-import { activeChatThreadItem as activeChatThreadItemSelector } from '../selectors/chat-selectors.js';
+import { useActiveChatThreadItem } from '../selectors/chat-selectors.js';
type Props = {
+children: React.Node,
};
function WebEditThreadAvatarProvider(props: Props): React.Node {
const { children } = props;
- const activeChatThreadItem = useSelector(activeChatThreadItemSelector);
+ const activeChatThreadItem = useActiveChatThreadItem();
const activeThreadID = activeChatThreadItem?.threadInfo?.id ?? '';
return (
{children}
);
}
export default WebEditThreadAvatarProvider;
diff --git a/web/chat/thread-list-provider.js b/web/chat/thread-list-provider.js
index 90c1b30fe..cfabc8f2f 100644
--- a/web/chat/thread-list-provider.js
+++ b/web/chat/thread-list-provider.js
@@ -1,261 +1,261 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js';
import { useThreadListSearch } from 'lib/hooks/thread-search-hooks.js';
import {
type ChatThreadItem,
useFlattenedChatListData,
} from 'lib/selectors/chat-selectors.js';
import { threadInfoSelector } from 'lib/selectors/thread-selectors.js';
import {
threadInBackgroundChatList,
threadInHomeChatList,
getThreadListSearchResults,
threadIsPending,
useIsThreadInChatList,
} from 'lib/shared/thread-utils.js';
import { threadTypeIsSidebar } from 'lib/types/thread-types-enum.js';
import { useSelector } from '../redux/redux-utils.js';
import {
useChatThreadItem,
- activeChatThreadItem as activeChatThreadItemSelector,
+ useActiveChatThreadItem,
} from '../selectors/chat-selectors.js';
type ChatTabType = 'Home' | 'Muted';
type ThreadListContextType = {
+activeTab: ChatTabType,
+setActiveTab: (newActiveTab: ChatTabType) => void,
+threadList: $ReadOnlyArray,
+searchText: string,
+setSearchText: (searchText: string) => void,
};
const ThreadListContext: React.Context =
React.createContext();
type ThreadListProviderProps = {
+children: React.Node,
};
function ThreadListProvider(props: ThreadListProviderProps): React.Node {
const [activeTab, setActiveTab] = React.useState('Home');
- const activeChatThreadItem = useSelector(activeChatThreadItemSelector);
+ const activeChatThreadItem = useActiveChatThreadItem();
const activeThreadInfo = activeChatThreadItem?.threadInfo;
const activeThreadID = activeThreadInfo?.id;
const activeSidebarParentThreadInfo = useSelector(state => {
if (!activeThreadInfo || !threadTypeIsSidebar(activeThreadInfo.type)) {
return null;
}
const { parentThreadID } = activeThreadInfo;
invariant(parentThreadID, 'sidebar must have parent thread');
return threadInfoSelector(state)[parentThreadID];
});
const activeTopLevelThreadInfo =
activeThreadInfo && threadTypeIsSidebar(activeThreadInfo?.type)
? activeSidebarParentThreadInfo
: activeThreadInfo;
const activeTopLevelThreadIsFromHomeTab =
activeTopLevelThreadInfo?.currentUser.subscription.home;
const activeTopLevelThreadIsFromDifferentTab =
(activeTab === 'Home' && activeTopLevelThreadIsFromHomeTab) ||
(activeTab === 'Muted' && !activeTopLevelThreadIsFromHomeTab);
const activeTopLevelThreadIsInChatList = useIsThreadInChatList(
activeTopLevelThreadInfo,
);
const shouldChangeTab =
activeTopLevelThreadIsInChatList && activeTopLevelThreadIsFromDifferentTab;
const prevActiveThreadIDRef = React.useRef();
React.useEffect(() => {
const prevActiveThreadID = prevActiveThreadIDRef.current;
prevActiveThreadIDRef.current = activeThreadID;
if (activeThreadID !== prevActiveThreadID && shouldChangeTab) {
setActiveTab(activeTopLevelThreadIsFromHomeTab ? 'Home' : 'Muted');
}
}, [activeThreadID, shouldChangeTab, activeTopLevelThreadIsFromHomeTab]);
const activeThreadOriginalTab = React.useMemo(() => {
if (activeTopLevelThreadIsInChatList) {
return null;
}
return activeTab;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeTopLevelThreadIsInChatList, activeThreadID]);
const makeSureActivePendingThreadIsIncluded = React.useCallback(
(
threadListData: $ReadOnlyArray,
): $ReadOnlyArray => {
if (
activeChatThreadItem &&
threadIsPending(activeThreadID) &&
(!activeThreadInfo || !threadTypeIsSidebar(activeThreadInfo.type)) &&
!threadListData
.map(thread => thread.threadInfo.id)
.includes(activeThreadID)
) {
return [activeChatThreadItem, ...threadListData];
}
return threadListData;
},
[activeChatThreadItem, activeThreadID, activeThreadInfo],
);
const makeSureActiveSidebarIsIncluded = React.useCallback(
(threadListData: $ReadOnlyArray) => {
if (
!activeChatThreadItem ||
!threadTypeIsSidebar(activeChatThreadItem.threadInfo.type)
) {
return threadListData;
}
const sidebarParentIndex = threadListData.findIndex(
thread =>
thread.threadInfo.id ===
activeChatThreadItem.threadInfo.parentThreadID,
);
if (sidebarParentIndex === -1) {
return threadListData;
}
const parentItem = threadListData[sidebarParentIndex];
for (const sidebarItem of parentItem.sidebars) {
if (sidebarItem.type !== 'sidebar') {
continue;
} else if (
sidebarItem.threadInfo.id === activeChatThreadItem.threadInfo.id
) {
return threadListData;
}
}
let indexToInsert = parentItem.sidebars.findIndex(
sidebar =>
sidebar.lastUpdatedTime === undefined ||
sidebar.lastUpdatedTime < activeChatThreadItem.lastUpdatedTime,
);
if (indexToInsert === -1) {
indexToInsert = parentItem.sidebars.length;
}
const activeSidebar = {
type: 'sidebar',
lastUpdatedTime: activeChatThreadItem.lastUpdatedTime,
mostRecentNonLocalMessage:
activeChatThreadItem.mostRecentNonLocalMessage,
threadInfo: activeChatThreadItem.threadInfo,
};
const newSidebarItems = [...parentItem.sidebars];
newSidebarItems.splice(indexToInsert, 0, activeSidebar);
const newThreadListData = [...threadListData];
newThreadListData[sidebarParentIndex] = {
...parentItem,
sidebars: newSidebarItems,
};
return newThreadListData;
},
[activeChatThreadItem],
);
const chatListData = useFlattenedChatListData();
const [searchText, setSearchText] = React.useState('');
const loggedInUserInfo = useLoggedInUserInfo();
const viewerID = loggedInUserInfo?.id;
const { threadSearchResults, usersSearchResults } = useThreadListSearch(
searchText,
viewerID,
);
const threadFilter =
activeTab === 'Muted' ? threadInBackgroundChatList : threadInHomeChatList;
const chatListDataWithoutFilter = React.useMemo(
() =>
getThreadListSearchResults(
chatListData,
searchText,
threadFilter,
threadSearchResults,
usersSearchResults,
loggedInUserInfo,
),
[
chatListData,
searchText,
threadFilter,
threadSearchResults,
usersSearchResults,
loggedInUserInfo,
],
);
const activeTopLevelChatThreadItem = useChatThreadItem(
activeTopLevelThreadInfo,
);
const threadList = React.useMemo(() => {
let threadListWithTopLevelItem = chatListDataWithoutFilter;
if (
activeTopLevelChatThreadItem &&
!activeTopLevelThreadIsInChatList &&
activeThreadOriginalTab === activeTab
) {
threadListWithTopLevelItem = [
activeTopLevelChatThreadItem,
...threadListWithTopLevelItem,
];
}
const threadListWithCurrentPendingThread =
makeSureActivePendingThreadIsIncluded(threadListWithTopLevelItem);
return makeSureActiveSidebarIsIncluded(threadListWithCurrentPendingThread);
}, [
activeTab,
activeThreadOriginalTab,
activeTopLevelChatThreadItem,
activeTopLevelThreadIsInChatList,
chatListDataWithoutFilter,
makeSureActivePendingThreadIsIncluded,
makeSureActiveSidebarIsIncluded,
]);
const isChatCreationMode = useSelector(
state => state.navInfo.chatMode === 'create',
);
const orderedThreadList = React.useMemo(() => {
if (!isChatCreationMode) {
return threadList;
}
return [
...threadList.filter(thread => thread.threadInfo.id === activeThreadID),
...threadList.filter(thread => thread.threadInfo.id !== activeThreadID),
];
}, [activeThreadID, isChatCreationMode, threadList]);
const threadListContext = React.useMemo(
() => ({
activeTab,
threadList: orderedThreadList,
setActiveTab,
searchText,
setSearchText,
}),
[activeTab, orderedThreadList, searchText],
);
return (
{props.children}
);
}
export { ThreadListProvider, ThreadListContext };
diff --git a/web/selectors/chat-selectors.js b/web/selectors/chat-selectors.js
index 22b7418e3..5a8879167 100644
--- a/web/selectors/chat-selectors.js
+++ b/web/selectors/chat-selectors.js
@@ -1,77 +1,52 @@
// @flow
import * as React from 'react';
-import { createSelector } from 'reselect';
import {
type ChatThreadItem,
createChatThreadItem,
messageInfoSelector,
} from 'lib/selectors/chat-selectors.js';
import { sidebarInfoSelector } from 'lib/selectors/sidebar-selectors.js';
import { threadInfoSelector } from 'lib/selectors/thread-selectors.js';
import { threadIsPending } from 'lib/shared/thread-utils.js';
-import type { MessageInfo, MessageStore } from 'lib/types/message-types.js';
import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js';
-import type { SidebarInfo } from 'lib/types/thread-types.js';
-import type { AppState } from '../redux/redux-setup.js';
import { useSelector } from '../redux/redux-utils.js';
-const activeChatThreadItem: (state: AppState) => ?ChatThreadItem =
- createSelector(
- threadInfoSelector,
- (state: AppState) => state.messageStore,
- messageInfoSelector,
- (state: AppState) => state.navInfo.activeChatThreadID,
- (state: AppState) => state.navInfo.pendingThread,
- sidebarInfoSelector,
- (
- threadInfos: {
- +[id: string]: ThreadInfo,
- },
- messageStore: MessageStore,
- messageInfos: { +[id: string]: ?MessageInfo },
- activeChatThreadID: ?string,
- pendingThreadInfo: ?ThreadInfo,
- sidebarInfos: { +[id: string]: $ReadOnlyArray },
- ): ?ChatThreadItem => {
- if (!activeChatThreadID) {
- return null;
- }
- const isPending = threadIsPending(activeChatThreadID);
- const threadInfo = isPending
- ? pendingThreadInfo
- : threadInfos[activeChatThreadID];
-
- if (!threadInfo) {
- return null;
- }
- return createChatThreadItem(
- threadInfo,
- messageStore,
- messageInfos,
- sidebarInfos[threadInfo.id],
- );
- },
- );
-
function useChatThreadItem(threadInfo: ?ThreadInfo): ?ChatThreadItem {
const messageInfos = useSelector(messageInfoSelector);
const sidebarInfos = useSelector(sidebarInfoSelector);
const messageStore = useSelector(state => state.messageStore);
return React.useMemo(() => {
if (!threadInfo) {
return null;
}
return createChatThreadItem(
threadInfo,
messageStore,
messageInfos,
sidebarInfos[threadInfo.id],
);
}, [messageInfos, messageStore, sidebarInfos, threadInfo]);
}
-export { useChatThreadItem, activeChatThreadItem };
+
+function useActiveChatThreadItem(): ?ChatThreadItem {
+ const activeChatThreadID = useSelector(
+ state => state.navInfo.activeChatThreadID,
+ );
+ const pendingThreadInfo = useSelector(state => state.navInfo.pendingThread);
+ const threadInfos = useSelector(threadInfoSelector);
+ const threadInfo = React.useMemo(() => {
+ if (!activeChatThreadID) {
+ return null;
+ }
+ const isPending = threadIsPending(activeChatThreadID);
+ return isPending ? pendingThreadInfo : threadInfos[activeChatThreadID];
+ }, [activeChatThreadID, pendingThreadInfo, threadInfos]);
+ return useChatThreadItem(threadInfo);
+}
+
+export { useChatThreadItem, useActiveChatThreadItem };