diff --git a/web/chat/chat-message-list-container.react.js b/web/chat/chat-message-list-container.react.js --- a/web/chat/chat-message-list-container.react.js +++ b/web/chat/chat-message-list-container.react.js @@ -2,7 +2,6 @@ import classNames from 'classnames'; import invariant from 'invariant'; -import _isEqual from 'lodash/fp/isEqual.js'; import * as React from 'react'; import { useDrop } from 'react-dnd'; import { NativeTypes } from 'react-dnd-html5-backend'; @@ -28,46 +27,14 @@ function ChatMessageListContainer(props: Props): React.Node { const { activeChatThreadID } = props; - const { - isChatCreation, - selectedUserIDs, - otherUserInfos, - userInfoInputArray, - } = useInfosForPendingThread(); + const { isChatCreation, selectedUserInfos, otherUserInfos } = + useInfosForPendingThread(); const threadInfo = useThreadInfoForPossiblyPendingThread(activeChatThreadID); invariant(threadInfo, 'ThreadInfo should be set'); const dispatch = useDispatch(); - // The effect removes members from list in navInfo - // if some of the user IDs don't exist in redux store - React.useEffect(() => { - if (!isChatCreation) { - return; - } - const existingSelectedUsersSet = new Set( - userInfoInputArray.map(userInfo => userInfo.id), - ); - if ( - selectedUserIDs?.length !== existingSelectedUsersSet.size || - !_isEqual(new Set(selectedUserIDs), existingSelectedUsersSet) - ) { - dispatch({ - type: updateNavInfoActionType, - payload: { - selectedUserList: Array.from(existingSelectedUsersSet), - }, - }); - } - }, [ - dispatch, - isChatCreation, - otherUserInfos, - selectedUserIDs, - userInfoInputArray, - ]); - React.useEffect(() => { if (isChatCreation && activeChatThreadID !== threadInfo?.id) { let payload = { @@ -158,14 +125,14 @@ } const chatUserSelection = ( ); - if (!userInfoInputArray.length) { + if (!selectedUserInfos.length) { return chatUserSelection; } return ( @@ -179,8 +146,8 @@ inputState, isChatCreation, otherUserInfos, + selectedUserInfos, threadInfo, - userInfoInputArray, ]); return connectDropTarget( diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js --- a/web/chat/chat-thread-composer.react.js +++ b/web/chat/chat-thread-composer.react.js @@ -1,5 +1,7 @@ // @flow + import classNames from 'classnames'; +import _isEqual from 'lodash/fp/isEqual.js'; import * as React from 'react'; import { useDispatch } from 'react-redux'; @@ -57,12 +59,11 @@ const userListItemsWithENSNames = useENSNames(userListItems); const onSelectUserFromSearch = React.useCallback( - (id: string) => { - const selectedUserIDs = userInfoInputArray.map(user => user.id); + (user: AccountUserInfo) => { dispatch({ type: updateNavInfoActionType, payload: { - selectedUserList: [...selectedUserIDs, id], + selectedUserList: [...userInfoInputArray, user], }, }); setUsernameInputText(''); @@ -71,15 +72,17 @@ ); const onRemoveUserFromSelected = React.useCallback( - (id: string) => { - const selectedUserIDs = userInfoInputArray.map(user => user.id); - if (!selectedUserIDs.includes(id)) { + (userID: string) => { + const newSelectedUserList = userInfoInputArray.filter( + ({ id }) => userID !== id, + ); + if (_isEqual(userInfoInputArray)(newSelectedUserList)) { return; } dispatch({ type: updateNavInfoActionType, payload: { - selectedUserList: selectedUserIDs.filter(userID => userID !== id), + selectedUserList: newSelectedUserList, }, }); }, @@ -102,23 +105,27 @@ } const userItems = userListItemsWithENSNames.map( - (userSearchResult: UserListItem) => ( -
  • - -
  • - ), +
    {userSearchResult.alertTitle}
    + + + ); + }, ); return ; diff --git a/web/types/nav-types.js b/web/types/nav-types.js --- a/web/types/nav-types.js +++ b/web/types/nav-types.js @@ -7,6 +7,10 @@ type ThreadInfo, threadInfoValidator, } from 'lib/types/thread-types.js'; +import { + type AccountUserInfo, + accountUserInfoValidator, +} from 'lib/types/user-types.js'; import { tID, tShape } from 'lib/utils/validation-utils.js'; export type NavigationTab = 'calendar' | 'chat' | 'settings'; @@ -27,7 +31,7 @@ +activeChatThreadID: ?string, +pendingThread?: ThreadInfo, +settingsSection?: NavigationSettingsSection, - +selectedUserList?: $ReadOnlyArray, + +selectedUserList?: $ReadOnlyArray, +chatMode?: NavigationChatMode, +inviteSecret?: ?string, }; @@ -39,7 +43,7 @@ activeChatThreadID: t.maybe(tID), pendingThread: t.maybe(threadInfoValidator), settingsSection: t.maybe(navigationSettingsSectionValidator), - selectedUserList: t.maybe(t.list(t.String)), + selectedUserList: t.maybe(t.list(accountUserInfoValidator)), chatMode: t.maybe(navigationChatModeValidator), inviteSecret: t.maybe(t.String), }); diff --git a/web/url-utils.js b/web/url-utils.js --- a/web/url-utils.js +++ b/web/url-utils.js @@ -1,6 +1,7 @@ // @flow import invariant from 'invariant'; +import _keyBy from 'lodash/fp/keyBy.js'; import { startDateForYearAndMonth, @@ -55,7 +56,8 @@ } } else if (navInfo.tab === 'chat') { if (navInfo.chatMode === 'create') { - const users = navInfo.selectedUserList?.join('+') ?? ''; + const users = + navInfo.selectedUserList?.map(({ id }) => id)?.join('+') ?? ''; const potentiallyTrailingSlash = users.length > 0 ? '/' : ''; newURL += `thread/new/${users}${potentiallyTrailingSlash}`; } else { @@ -126,7 +128,10 @@ }; if (urlInfo.selectedUserList) { - newNavInfo.selectedUserList = urlInfo.selectedUserList; + const selectedUsers = _keyBy('id')(navInfo?.selectedUserList ?? []); + newNavInfo.selectedUserList = urlInfo.selectedUserList + ?.map(id => selectedUsers[id]) + ?.filter(Boolean); } if (urlInfo.settings) { diff --git a/web/utils/thread-utils.js b/web/utils/thread-utils.js --- a/web/utils/thread-utils.js +++ b/web/utils/thread-utils.js @@ -18,33 +18,29 @@ type InfosForPendingThread = { +isChatCreation: boolean, - +selectedUserIDs: ?$ReadOnlyArray, + +selectedUserInfos: $ReadOnlyArray, +otherUserInfos: { [id: string]: AccountUserInfo }, - +userInfoInputArray: $ReadOnlyArray, }; function useInfosForPendingThread(): InfosForPendingThread { const isChatCreation = useSelector( state => state.navInfo.chatMode === 'create', ); - const selectedUserIDs = useSelector(state => state.navInfo.selectedUserList); - const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); - const userInfoInputArray: $ReadOnlyArray = React.useMemo( - () => selectedUserIDs?.map(id => otherUserInfos[id]).filter(Boolean) ?? [], - [otherUserInfos, selectedUserIDs], + const selectedUserInfos = useSelector( + state => state.navInfo.selectedUserList ?? [], ); + const otherUserInfos = useSelector(userInfoSelectorForPotentialMembers); return { isChatCreation, - selectedUserIDs, + selectedUserInfos, otherUserInfos, - userInfoInputArray, }; } function useThreadInfoForPossiblyPendingThread( activeChatThreadID: ?string, ): ?ThreadInfo { - const { isChatCreation, userInfoInputArray } = useInfosForPendingThread(); + const { isChatCreation, selectedUserInfos } = useInfosForPendingThread(); const loggedInUserInfo = useLoggedInUserInfo(); invariant(loggedInUserInfo, 'loggedInUserInfo should be set'); @@ -86,13 +82,13 @@ const existingThreadInfoFinder = useExistingThreadInfoFinder(baseThreadInfo); const threadInfo = React.useMemo(() => { if (isChatCreation) { - if (userInfoInputArray.length === 0) { + if (selectedUserInfos.length === 0) { return pendingNewThread; } return existingThreadInfoFinderForCreatingThread({ searching: true, - userInfoInputArray, + userInfoInputArray: selectedUserInfos, }); } @@ -104,8 +100,8 @@ existingThreadInfoFinder, existingThreadInfoFinderForCreatingThread, isChatCreation, - userInfoInputArray, pendingNewThread, + selectedUserInfos, ]); return threadInfo;