From ea514b7d8136deb1f31443457bd454fd44c8d31e Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 22 Jan 2026 00:28:16 +0530 Subject: [PATCH 01/23] Search member in room using API --- app/views/RoomMembersView/index.tsx | 71 +++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index cacad557b09..8a47b6ee316 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -17,7 +17,7 @@ import { type IGetRoomRoles, type TSubscriptionModel, type TUserModel } from '.. import I18n from '../../i18n'; import { useAppSelector } from '../../lib/hooks/useAppSelector'; import { usePermissions } from '../../lib/hooks/usePermissions'; -import { compareServerVersion, getRoomTitle, isGroupChat } from '../../lib/methods/helpers'; +import { compareServerVersion, debounce, getRoomTitle, isGroupChat } from '../../lib/methods/helpers'; import { handleIgnore } from '../../lib/methods/helpers/handleIgnore'; import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import log from '../../lib/methods/helpers/log'; @@ -367,10 +367,21 @@ const RoomMembersView = (): React.ReactElement => { limit: PAGE_SIZE, allUsers: !status }); + const end = membersResult?.length < PAGE_SIZE; - const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id)); + + let newMembers: TUserModel[]; + if (filter) { + newMembers = membersResult || []; + } else { + const membersResultFiltered = membersResult?.filter((member: TUserModel) => + !members.some(m => m._id === member._id) + ); + newMembers = [...members, ...membersResultFiltered]; + } + updateState({ - members: [...members, ...membersResultFiltered], + members: newMembers, isLoading: false, end, page: page + 1 @@ -381,11 +392,53 @@ const RoomMembersView = (): React.ReactElement => { } }; - const filter = sanitizeLikeString(state.filter.toLowerCase()) || ''; - const filteredMembers = - state.members && state.members.length > 0 && state.filter - ? state.members.filter(m => m.username.toLowerCase().match(filter) || m.name?.toLowerCase().match(filter)) - : null; + const fetchMembersWithNewFilter = async (searchFilter: string) => { + const { room, allUsers } = state; + const { t } = room; + + updateState({ isLoading: true }); + try { + const membersResult = await getRoomMembers({ + rid: room.rid, + roomType: t, + type: !allUsers ? 'all' : 'online', + filter: searchFilter, + skip: 0, + limit: PAGE_SIZE, + allUsers: !allUsers + }); + + const end = membersResult?.length < PAGE_SIZE; + + updateState({ + members: membersResult || [], + isLoading: false, + end, + page: 1 + }); + } catch (e) { + log(e); + updateState({ isLoading: false }); + } + }; + + const handleFilterChange = (text: string) => { + const trimmedFilter = text.trim(); + + updateState({ + filter: trimmedFilter, + page: 0, + members: [], + end: false + }); + + if (trimmedFilter.length > 0) { + fetchMembersWithNewFilter(trimmedFilter); + } + }; + + const debounceFilterChange = debounce(handleFilterChange, 500); + const filteredMembers = state.members.length > 0 ? state.members : null; return ( @@ -412,7 +465,7 @@ const RoomMembersView = (): React.ReactElement => { t={state.room.t} abacAttributes={state.room.abacAttributes} /> - updateState({ filter: text.trim() })} testID='room-members-view-search' /> + debounceFilterChange(text)} testID='room-members-view-search' /> } ListFooterComponent={() => (state.isLoading ? : null)} From fd7e0fe16e1dd592f59dedeefcf9b9d2a0cd7f68 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:13:40 +0530 Subject: [PATCH 02/23] =?UTF-8?q?Guard=20against=20out=E2=80=91of=E2=80=91?= =?UTF-8?q?order=20search=20responses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/RoomMembersView/index.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 8a47b6ee316..17a6a152861 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -1,5 +1,5 @@ import { type NavigationProp, type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; -import React, { useEffect, useReducer } from 'react'; +import React, { useEffect, useReducer, useRef } from 'react'; import { FlatList, Text, View } from 'react-native'; import { shallowEqual } from 'react-redux'; @@ -76,6 +76,8 @@ const RoomMembersView = (): React.ReactElement => { const { params } = useRoute>(); const navigation = useNavigation>(); + const latestSearchRequest = useRef(0); + const { isMasterDetail, serverVersion, useRealName, user, loading } = useAppSelector( state => ({ isMasterDetail: state.app.isMasterDetail, @@ -393,6 +395,8 @@ const RoomMembersView = (): React.ReactElement => { }; const fetchMembersWithNewFilter = async (searchFilter: string) => { + const requestId = ++latestSearchRequest.current; + const { room, allUsers } = state; const { t } = room; @@ -410,6 +414,10 @@ const RoomMembersView = (): React.ReactElement => { const end = membersResult?.length < PAGE_SIZE; + if (requestId !== latestSearchRequest.current) { + return; + } + updateState({ members: membersResult || [], isLoading: false, @@ -418,7 +426,9 @@ const RoomMembersView = (): React.ReactElement => { }); } catch (e) { log(e); - updateState({ isLoading: false }); + if (requestId === latestSearchRequest.current) { + updateState({ isLoading: false }); + } } }; From ad736f764c4f2eba292d4734bb1ae8a5960ad5f1 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 22 Jan 2026 01:14:09 +0530 Subject: [PATCH 03/23] Lint fix --- app/views/RoomMembersView/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 17a6a152861..476440b0570 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -41,7 +41,6 @@ import { type TRoomType } from './helpers'; import styles from './styles'; -import { sanitizeLikeString } from '../../lib/database/utils'; const PAGE_SIZE = 25; From a3aeda5249eb0dd8034ee3ec637dde185c4d12b2 Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Wed, 21 Jan 2026 19:47:05 +0000 Subject: [PATCH 04/23] chore: format code and fix lint issues [skip ci] --- app/views/RoomMembersView/index.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 476440b0570..e10a66512c8 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -75,7 +75,7 @@ const RoomMembersView = (): React.ReactElement => { const { params } = useRoute>(); const navigation = useNavigation>(); - const latestSearchRequest = useRef(0); + const latestSearchRequest = useRef(0); const { isMasterDetail, serverVersion, useRealName, user, loading } = useAppSelector( state => ({ @@ -375,9 +375,7 @@ const RoomMembersView = (): React.ReactElement => { if (filter) { newMembers = membersResult || []; } else { - const membersResultFiltered = membersResult?.filter((member: TUserModel) => - !members.some(m => m._id === member._id) - ); + const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id)); newMembers = [...members, ...membersResultFiltered]; } @@ -394,7 +392,7 @@ const RoomMembersView = (): React.ReactElement => { }; const fetchMembersWithNewFilter = async (searchFilter: string) => { - const requestId = ++latestSearchRequest.current; + const requestId = ++latestSearchRequest.current; const { room, allUsers } = state; const { t } = room; @@ -425,7 +423,7 @@ const RoomMembersView = (): React.ReactElement => { }); } catch (e) { log(e); - if (requestId === latestSearchRequest.current) { + if (requestId === latestSearchRequest.current) { updateState({ isLoading: false }); } } From 3820bd72690bc18c85894ca39d5e16e6d0658531 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:56:41 +0530 Subject: [PATCH 05/23] merge search result --- app/views/RoomMembersView/index.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index e10a66512c8..dcf007d99a8 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -371,13 +371,8 @@ const RoomMembersView = (): React.ReactElement => { const end = membersResult?.length < PAGE_SIZE; - let newMembers: TUserModel[]; - if (filter) { - newMembers = membersResult || []; - } else { - const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id)); - newMembers = [...members, ...membersResultFiltered]; - } + const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id)); + const newMembers = [...members, ...(membersResultFiltered || [])]; updateState({ members: newMembers, From f2edc75bb8b989fda48ed7a899ac53e195a2d938 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:28:17 +0530 Subject: [PATCH 06/23] use debounce hook --- app/views/RoomMembersView/index.tsx | 33 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index dcf007d99a8..b34fb9f3c11 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -17,7 +17,7 @@ import { type IGetRoomRoles, type TSubscriptionModel, type TUserModel } from '.. import I18n from '../../i18n'; import { useAppSelector } from '../../lib/hooks/useAppSelector'; import { usePermissions } from '../../lib/hooks/usePermissions'; -import { compareServerVersion, debounce, getRoomTitle, isGroupChat } from '../../lib/methods/helpers'; +import { compareServerVersion, getRoomTitle, isGroupChat, useDebounce } from '../../lib/methods/helpers'; import { handleIgnore } from '../../lib/methods/helpers/handleIgnore'; import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import log from '../../lib/methods/helpers/log'; @@ -168,6 +168,21 @@ const RoomMembersView = (): React.ReactElement => { viewAllTeamsPermission ]); + const debounceFilterChange = useDebounce((text: string) => { + const trimmedFilter = text.trim(); + + updateState({ + filter: trimmedFilter, + page: 0, + members: [], + end: false + }); + + if (trimmedFilter.length > 0) { + fetchMembersWithNewFilter(trimmedFilter); + } + }, 500); + const toggleStatus = (status: boolean) => { try { updateState({ members: [], allUsers: status, end: false }); @@ -424,22 +439,6 @@ const RoomMembersView = (): React.ReactElement => { } }; - const handleFilterChange = (text: string) => { - const trimmedFilter = text.trim(); - - updateState({ - filter: trimmedFilter, - page: 0, - members: [], - end: false - }); - - if (trimmedFilter.length > 0) { - fetchMembersWithNewFilter(trimmedFilter); - } - }; - - const debounceFilterChange = debounce(handleFilterChange, 500); const filteredMembers = state.members.length > 0 ? state.members : null; return ( From d4839a81b1195c58121c211fbd02950261802b50 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:38:16 +0530 Subject: [PATCH 07/23] Prevent stale member updates when the filter changes or clears --- app/views/RoomMembersView/index.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index b34fb9f3c11..5cdd5acec7e 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -171,11 +171,16 @@ const RoomMembersView = (): React.ReactElement => { const debounceFilterChange = useDebounce((text: string) => { const trimmedFilter = text.trim(); + if (!trimmedFilter) { + latestSearchRequest.current += 1; + } + updateState({ filter: trimmedFilter, page: 0, members: [], - end: false + end: false, + isLoading: false }); if (trimmedFilter.length > 0) { @@ -372,6 +377,7 @@ const RoomMembersView = (): React.ReactElement => { return; } + const requestId = ++latestSearchRequest.current; updateState({ isLoading: true }); try { const membersResult = await getRoomMembers({ @@ -384,6 +390,10 @@ const RoomMembersView = (): React.ReactElement => { allUsers: !status }); + if (requestId !== latestSearchRequest.current) { + return; + } + const end = membersResult?.length < PAGE_SIZE; const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id)); @@ -397,7 +407,9 @@ const RoomMembersView = (): React.ReactElement => { }); } catch (e) { log(e); - updateState({ isLoading: false }); + if (requestId === latestSearchRequest.current) { + updateState({ isLoading: false }); + } } }; From 1492becf7abbca87ad5d3d47d73a7581c653019a Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:55:22 +0530 Subject: [PATCH 08/23] Use members from state --- app/views/RoomMembersView/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 5cdd5acec7e..52fad4fe71e 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -451,12 +451,10 @@ const RoomMembersView = (): React.ReactElement => { } }; - const filteredMembers = state.members.length > 0 ? state.members : null; - return ( ( Date: Fri, 23 Jan 2026 23:45:22 +0530 Subject: [PATCH 09/23] Added E2E test for search member --- .maestro/tests/room/search-member.yaml | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .maestro/tests/room/search-member.yaml diff --git a/.maestro/tests/room/search-member.yaml b/.maestro/tests/room/search-member.yaml new file mode 100644 index 00000000000..4ac196f3602 --- /dev/null +++ b/.maestro/tests/room/search-member.yaml @@ -0,0 +1,39 @@ +appId: chat.rocket.reactnative +name: Search Member +onFlowStart: + - runFlow: '../../helpers/setup.yaml' +tags: + - test-13 + +--- +- evalScript: ${output.user = output.utils.createUser()} + +- runFlow: + file: '../../helpers/login-with-deeplink.yaml' + env: + USERNAME: ${output.user.username} + PASSWORD: ${output.user.password} + +- runFlow: + file: '../../helpers/navigate-to-room.yaml' + env: + ROOM: 'general' +- tapOn: + id: room-header +- extendedWaitUntil: + visible: + id: 'room-actions-view' + timeout: 60000 +- tapOn: + id: 'room-actions-members' +- extendedWaitUntil: + visible: + id: 'room-members-view-search' + timeout: 60000 +- tapOn: + id: room-members-view-search +- inputText: rohit.bansal +- extendedWaitUntil: + visible: + id: 'room-members-view-item-rohit.bansal' + timeout: 60000 From 4f36a02b95cab0e67ae655a868ec10cd898b0a20 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 24 Jan 2026 02:28:30 +0530 Subject: [PATCH 10/23] combine filter text with status filter --- app/views/RoomMembersView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 52fad4fe71e..be39ac73631 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -190,7 +190,7 @@ const RoomMembersView = (): React.ReactElement => { const toggleStatus = (status: boolean) => { try { - updateState({ members: [], allUsers: status, end: false }); + updateState({ members: [], allUsers: status, end: false, page: 0 }); fetchMembers(status); setHeader(status); } catch (e) { From cc8f8b4e8405cf3dff13a7f5049b6d6ab6306602 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 24 Jan 2026 03:39:36 +0530 Subject: [PATCH 11/23] test update --- .maestro/tests/room/search-member.yaml | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.maestro/tests/room/search-member.yaml b/.maestro/tests/room/search-member.yaml index 4ac196f3602..9ecbfc079fb 100644 --- a/.maestro/tests/room/search-member.yaml +++ b/.maestro/tests/room/search-member.yaml @@ -30,6 +30,8 @@ tags: visible: id: 'room-members-view-search' timeout: 60000 + +# should search in all users - tapOn: id: room-members-view-search - inputText: rohit.bansal @@ -37,3 +39,31 @@ tags: visible: id: 'room-members-view-item-rohit.bansal' timeout: 60000 + +# use online status and it should use the text filter +- tapOn: + id: room-members-view-filter +- extendedWaitUntil: + visible: + id: 'room-members-view-toggle-status-online' + timeout: 60000 +- tapOn: + id: room-members-view-toggle-status-online +- extendedWaitUntil: + visible: + text: 'No members found' + timeout: 60000 + +# use all status again and it should use text filter +- tapOn: + id: room-members-view-filter +- extendedWaitUntil: + visible: + id: 'room-members-view-toggle-status-all' + timeout: 60000 +- tapOn: + id: room-members-view-toggle-status-all +- extendedWaitUntil: + visible: + id: 'room-members-view-item-rohit.bansal' + timeout: 60000 From cd9210a759a71b59353fe457ebe9d92c8bbeba7b Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:58:01 +0530 Subject: [PATCH 12/23] Test update --- .maestro/tests/room/search-member.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.maestro/tests/room/search-member.yaml b/.maestro/tests/room/search-member.yaml index 9ecbfc079fb..5f710e9e840 100644 --- a/.maestro/tests/room/search-member.yaml +++ b/.maestro/tests/room/search-member.yaml @@ -67,3 +67,16 @@ tags: visible: id: 'room-members-view-item-rohit.bansal' timeout: 60000 +- tapOn: + id: clear-text-input + +- evalScript: ${output.secondUser = output.utils.createUser()} + +# should search for new user in all list +- tapOn: + id: room-members-view-search +- inputText: ${output.secondUser.username} +- extendedWaitUntil: + visible: + id: 'room-members-view-item-${output.secondUser.username}' + timeout: 60000 From 831d6d03e466a4f25315d5d7a66a2abd28879187 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 24 Jan 2026 20:58:18 +0530 Subject: [PATCH 13/23] status filter is not using search text --- app/views/RoomMembersView/index.tsx | 73 +++++++---------------------- 1 file changed, 16 insertions(+), 57 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index be39ac73631..3fa50e68b9b 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -96,7 +96,7 @@ const RoomMembersView = (): React.ReactElement => { (state: IRoomMembersViewState, newState: Partial) => ({ ...state, ...newState }), { isLoading: false, - allUsers: false, + allUsers: true, filtering: '', members: [], room: params.room || ({} as TSubscriptionModel), @@ -124,15 +124,13 @@ const RoomMembersView = (): React.ReactElement => { useEffect(() => { const subscription = params?.room?.observe && params.room.observe().subscribe(changes => updateState({ room: changes })); - setHeader(false); - fetchMembers(false); + setHeader(true); return () => subscription?.unsubscribe(); }, []); useEffect(() => { const unsubscribe = navigation.addListener('focus', () => { - const { allUsers } = state; - fetchMembers(allUsers); + fetchMembers(); }); return unsubscribe; @@ -168,6 +166,10 @@ const RoomMembersView = (): React.ReactElement => { viewAllTeamsPermission ]); + useEffect(() => { + fetchMembers(); + }, [state.filter, state.allUsers]); + const debounceFilterChange = useDebounce((text: string) => { const trimmedFilter = text.trim(); @@ -182,16 +184,11 @@ const RoomMembersView = (): React.ReactElement => { end: false, isLoading: false }); - - if (trimmedFilter.length > 0) { - fetchMembersWithNewFilter(trimmedFilter); - } }, 500); const toggleStatus = (status: boolean) => { try { updateState({ members: [], allUsers: status, end: false, page: 0 }); - fetchMembers(status); setHeader(status); } catch (e) { log(e); @@ -210,14 +207,14 @@ const RoomMembersView = (): React.ReactElement => { options: [ { title: I18n.t('Online'), - onPress: () => toggleStatus(true), - right: () => , + onPress: () => toggleStatus(false), + right: () => , testID: 'room-members-view-toggle-status-online' }, { title: I18n.t('All'), - onPress: () => toggleStatus(false), - right: () => , + onPress: () => toggleStatus(true), + right: () => , testID: 'room-members-view-toggle-status-all' } ] @@ -369,8 +366,8 @@ const RoomMembersView = (): React.ReactElement => { }); }; - const fetchMembers = async (status: boolean) => { - const { members, isLoading, end, room, filter, page } = state; + const fetchMembers = async () => { + const { members, isLoading, end, room, filter, page, allUsers } = state; const { t } = room; if (isLoading || end) { @@ -383,11 +380,11 @@ const RoomMembersView = (): React.ReactElement => { const membersResult = await getRoomMembers({ rid: room.rid, roomType: t, - type: !status ? 'all' : 'online', + type: allUsers ? 'all' : 'online', filter, skip: PAGE_SIZE * page, limit: PAGE_SIZE, - allUsers: !status + allUsers: allUsers }); if (requestId !== latestSearchRequest.current) { @@ -413,44 +410,6 @@ const RoomMembersView = (): React.ReactElement => { } }; - const fetchMembersWithNewFilter = async (searchFilter: string) => { - const requestId = ++latestSearchRequest.current; - - const { room, allUsers } = state; - const { t } = room; - - updateState({ isLoading: true }); - try { - const membersResult = await getRoomMembers({ - rid: room.rid, - roomType: t, - type: !allUsers ? 'all' : 'online', - filter: searchFilter, - skip: 0, - limit: PAGE_SIZE, - allUsers: !allUsers - }); - - const end = membersResult?.length < PAGE_SIZE; - - if (requestId !== latestSearchRequest.current) { - return; - } - - updateState({ - members: membersResult || [], - isLoading: false, - end, - page: 1 - }); - } catch (e) { - log(e); - if (requestId === latestSearchRequest.current) { - updateState({ isLoading: false }); - } - } - }; - return ( { } ListFooterComponent={() => (state.isLoading ? : null)} onEndReachedThreshold={0.1} - onEndReached={() => fetchMembers(state.allUsers)} + onEndReached={() => fetchMembers()} ListEmptyComponent={() => state.end ? ( {I18n.t('No_members_found')} From a0a2ec8e437181c4b4804815ebd494ef3ea225bd Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Sat, 24 Jan 2026 15:29:54 +0000 Subject: [PATCH 14/23] chore: format code and fix lint issues [skip ci] --- app/views/RoomMembersView/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 3fa50e68b9b..b7a1aa2a1d9 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -166,9 +166,9 @@ const RoomMembersView = (): React.ReactElement => { viewAllTeamsPermission ]); - useEffect(() => { - fetchMembers(); - }, [state.filter, state.allUsers]); + useEffect(() => { + fetchMembers(); + }, [state.filter, state.allUsers]); const debounceFilterChange = useDebounce((text: string) => { const trimmedFilter = text.trim(); @@ -384,7 +384,7 @@ const RoomMembersView = (): React.ReactElement => { filter, skip: PAGE_SIZE * page, limit: PAGE_SIZE, - allUsers: allUsers + allUsers }); if (requestId !== latestSearchRequest.current) { From 8bcb82c75051c5f625d6eca393319f4a8367b13f Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:13:56 +0530 Subject: [PATCH 15/23] Added state room in effect --- app/views/RoomMembersView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index b7a1aa2a1d9..d65ca2db24a 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -168,7 +168,7 @@ const RoomMembersView = (): React.ReactElement => { useEffect(() => { fetchMembers(); - }, [state.filter, state.allUsers]); + }, [state.filter, state.allUsers, state.room]); const debounceFilterChange = useDebounce((text: string) => { const trimmedFilter = text.trim(); From 47da4ec7460563ff4f85aebfc98416f31eb4a7fc Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Mon, 26 Jan 2026 16:32:22 -0300 Subject: [PATCH 16/23] chore: code improvements --- app/views/RoomMembersView/index.tsx | 817 ++++++++++++++-------------- 1 file changed, 411 insertions(+), 406 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index d65ca2db24a..00f365cc7a2 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -1,5 +1,5 @@ import { type NavigationProp, type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; -import React, { useEffect, useReducer, useRef } from 'react'; +import React, { useCallback, useEffect, useReducer, useRef } from 'react'; import { FlatList, Text, View } from 'react-native'; import { shallowEqual } from 'react-redux'; @@ -29,427 +29,432 @@ import { type ModalStackParamList } from '../../stacks/MasterDetailStack/types'; import { useTheme } from '../../theme'; import ActionsSection from './components/ActionsSection'; import { - fetchRole, - fetchRoomMembersRoles, - handleLeader, - handleModerator, - handleMute, - handleOwner, - handleRemoveFromTeam, - handleRemoveUserFromRoom, - navToDirectMessage, - type TRoomType + fetchRole, + fetchRoomMembersRoles, + handleLeader, + handleModerator, + handleMute, + handleOwner, + handleRemoveFromTeam, + handleRemoveUserFromRoom, + navToDirectMessage, + type TRoomType } from './helpers'; import styles from './styles'; const PAGE_SIZE = 25; interface IRoomMembersViewState { - isLoading: boolean; - allUsers: boolean; - filtering: string; - members: TUserModel[]; - room: TSubscriptionModel; - end: boolean; - roomRoles?: IGetRoomRoles[]; - filter: string; - page: number; + isLoading: boolean; + allUsers: boolean; + filtering: string; + members: TUserModel[]; + room: TSubscriptionModel; + end: boolean; + roomRoles?: IGetRoomRoles[]; + filter: string; + page: number; } const RightIcon = ({ check, label }: { check: boolean; label: string }) => { - const { colors } = useTheme(); - return ( - - ); + const { colors } = useTheme(); + return ( + + ); }; const RoomMembersView = (): React.ReactElement => { - const { showActionSheet } = useActionSheet(); - const { colors } = useTheme(); - - const { params } = useRoute>(); - const navigation = useNavigation>(); - - const latestSearchRequest = useRef(0); - - const { isMasterDetail, serverVersion, useRealName, user, loading } = useAppSelector( - state => ({ - isMasterDetail: state.app.isMasterDetail, - useRealName: state.settings.UI_Use_Real_Name, - user: getUserSelector(state), - serverVersion: state.server.version, - loading: state.selectedUsers.loading - }), - shallowEqual - ); - - useEffect(() => { - sendLoadingEvent({ visible: loading }); - }, [loading]); - - const [state, updateState] = useReducer( - (state: IRoomMembersViewState, newState: Partial) => ({ ...state, ...newState }), - { - isLoading: false, - allUsers: true, - filtering: '', - members: [], - room: params.room || ({} as TSubscriptionModel), - end: false, - roomRoles: undefined, - filter: '', - page: 0 - } - ); - - const teamPermissions: TSupportedPermissions[] = state.room.teamMain - ? ['edit-team-member', 'view-all-team-channels', 'view-all-teams'] - : []; - - const [ - muteUserPermission, - setLeaderPermission, - setOwnerPermission, - setModeratorPermission, - removeUserPermission, - editTeamMemberPermission, - viewAllTeamChannelsPermission, - viewAllTeamsPermission - ] = usePermissions(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user', ...teamPermissions], params.rid); - - useEffect(() => { - const subscription = params?.room?.observe && params.room.observe().subscribe(changes => updateState({ room: changes })); - setHeader(true); - return () => subscription?.unsubscribe(); - }, []); - - useEffect(() => { - const unsubscribe = navigation.addListener('focus', () => { - fetchMembers(); - }); - - return unsubscribe; - }, [navigation]); - - useEffect(() => { - const fetchRoles = () => { - if (isGroupChat(state.room)) { - return; - } - if ( - muteUserPermission || - setLeaderPermission || - setOwnerPermission || - setModeratorPermission || - removeUserPermission || - editTeamMemberPermission || - viewAllTeamChannelsPermission || - viewAllTeamsPermission - ) { - fetchRoomMembersRoles(state.room.t as any, state.room.rid, updateState); - } - }; - fetchRoles(); - }, [ - muteUserPermission, - setLeaderPermission, - setOwnerPermission, - setModeratorPermission, - removeUserPermission, - editTeamMemberPermission, - viewAllTeamChannelsPermission, - viewAllTeamsPermission - ]); - - useEffect(() => { - fetchMembers(); - }, [state.filter, state.allUsers, state.room]); - - const debounceFilterChange = useDebounce((text: string) => { - const trimmedFilter = text.trim(); + const { showActionSheet } = useActionSheet(); + const { colors } = useTheme(); + + const { params } = useRoute>(); + const navigation = useNavigation>(); + + const latestSearchRequest = useRef(0); + + const { isMasterDetail, serverVersion, useRealName, user, loading } = useAppSelector( + state => ({ + isMasterDetail: state.app.isMasterDetail, + useRealName: state.settings.UI_Use_Real_Name, + user: getUserSelector(state), + serverVersion: state.server.version, + loading: state.selectedUsers.loading + }), + shallowEqual + ); + + useEffect(() => { + sendLoadingEvent({ visible: loading }); + }, [loading]); + + const [state, updateState] = useReducer( + (state: IRoomMembersViewState, newState: Partial) => ({ ...state, ...newState }), + { + isLoading: false, + allUsers: true, + filtering: '', + members: [], + room: params.room || ({} as TSubscriptionModel), + end: false, + roomRoles: undefined, + filter: '', + page: 0 + } + ); + + const teamPermissions: TSupportedPermissions[] = state.room.teamMain + ? ['edit-team-member', 'view-all-team-channels', 'view-all-teams'] + : []; + + const [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission + ] = usePermissions(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user', ...teamPermissions], params.rid); + + useEffect(() => { + const subscription = params?.room?.observe && params.room.observe().subscribe(changes => updateState({ room: changes })); + setHeader(true); + return () => subscription?.unsubscribe(); + }, []); + + const fetchMembers = useCallback(async () => { + const { members, isLoading, end, room, filter, page, allUsers } = state; + const { t } = room; + + if (isLoading || end) { + return; + } + + const requestId = ++latestSearchRequest.current; + updateState({ isLoading: true }); + + try { + const membersResult = await getRoomMembers({ + rid: room.rid, + roomType: t, + type: allUsers ? 'all' : 'online', + filter, + skip: PAGE_SIZE * page, + limit: PAGE_SIZE, + allUsers + }); + + if (requestId !== latestSearchRequest.current) { + return; + } + + const existingIds = new Set(members.map(m => m._id)); + const membersResultFiltered = membersResult?.filter((member: TUserModel) => !existingIds.has(member._id)); + + // Safety check: if page is 0, we replace the list entirely + const newMembers = page === 0 ? membersResultFiltered : [...members, ...(membersResultFiltered || [])]; + const isEnd = membersResult?.length < PAGE_SIZE; + + updateState({ + members: newMembers, + isLoading: false, + end: isEnd, + page: page + 1 + }); + } catch (e) { + log(e); + if (requestId === latestSearchRequest.current) { + updateState({ isLoading: false }); + } + } + }, [state.members, state.isLoading, state.end, state.room, state.filter, state.page, state.allUsers]); + + useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + fetchMembers(); + }); + + return unsubscribe; + }, [navigation, fetchMembers]); + + useEffect(() => { + const fetchRoles = () => { + if (isGroupChat(state.room)) { + return; + } + if ( + muteUserPermission || + setLeaderPermission || + setOwnerPermission || + setModeratorPermission || + removeUserPermission || + editTeamMemberPermission || + viewAllTeamChannelsPermission || + viewAllTeamsPermission + ) { + fetchRoomMembersRoles(state.room.t as any, state.room.rid, updateState); + } + }; + fetchRoles(); + }, [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission, + state.room + ]); + + useEffect(() => { + fetchMembers(); + }, [state.filter, state.allUsers]); + + const debounceFilterChange = useDebounce((text: string) => { + const trimmedFilter = text.trim(); if (!trimmedFilter) { latestSearchRequest.current += 1; } - updateState({ - filter: trimmedFilter, - page: 0, - members: [], - end: false, - isLoading: false - }); - }, 500); - - const toggleStatus = (status: boolean) => { - try { - updateState({ members: [], allUsers: status, end: false, page: 0 }); - setHeader(status); - } catch (e) { - log(e); - } - }; - - const setHeader = (allUsers: boolean) => { - navigation.setOptions({ - title: I18n.t('Members'), - headerRight: () => ( - - - showActionSheet({ - options: [ - { - title: I18n.t('Online'), - onPress: () => toggleStatus(false), - right: () => , - testID: 'room-members-view-toggle-status-online' - }, - { - title: I18n.t('All'), - onPress: () => toggleStatus(true), - right: () => , - testID: 'room-members-view-toggle-status-all' - } - ] - }) - } - testID='room-members-view-filter' - /> - - ) - }); - }; - - const getUserDisplayName = (user: TUserModel) => (useRealName ? user.name : user.username) || user.username; - - const onPressUser = (selectedUser: TUserModel) => { - const { room, roomRoles, members } = state; - - const options: TActionSheetOptionsItem[] = [ - { - icon: 'message', - title: I18n.t('Direct_message'), - onPress: () => navToDirectMessage(selectedUser, isMasterDetail) - } - ]; - - // Owner - if (setOwnerPermission) { - const isOwner = fetchRole('owner', selectedUser, roomRoles); - options.push({ - icon: 'shield-check', - title: I18n.t('Owner'), - onPress: () => - handleOwner(selectedUser, !isOwner, getUserDisplayName(selectedUser), room, () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) - ), - right: () => , - testID: 'action-sheet-set-owner' - }); - } - - // Leader - if (setLeaderPermission) { - const isLeader = fetchRole('leader', selectedUser, roomRoles); - options.push({ - icon: 'shield-alt', - title: I18n.t('Leader'), - onPress: () => - handleLeader(selectedUser, !isLeader, room, getUserDisplayName(selectedUser), () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) - ), - right: () => , - testID: 'action-sheet-set-leader' - }); - } - - // Moderator - if (setModeratorPermission) { - const isModerator = fetchRole('moderator', selectedUser, roomRoles); - options.push({ - icon: 'shield', - title: I18n.t('Moderator'), - onPress: () => - handleModerator(selectedUser, !isModerator, room, getUserDisplayName(selectedUser), () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) - ), - right: () => , - testID: 'action-sheet-set-moderator' - }); - } - - if (muteUserPermission) { - const { muted = [], ro: readOnly, unmuted = [] } = room; - let userIsMuted = !!muted.find?.(m => m === selectedUser.username); - let icon: TIconsName = userIsMuted ? 'audio' : 'audio-disabled'; - let title = I18n.t(userIsMuted ? 'Unmute' : 'Mute'); - if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.4.0')) { - if (readOnly) { - userIsMuted = !unmuted?.find?.(m => m === selectedUser.username); - } - icon = userIsMuted ? 'message' : 'message-disabled'; - title = I18n.t(userIsMuted ? 'Enable_writing_in_room' : 'Disable_writing_in_room'); - } - selectedUser.muted = !!userIsMuted; - options.push({ - icon, - title, - onPress: () => { - showConfirmationAlert({ - message: I18n.t(`The_user_${userIsMuted ? 'will' : 'wont'}_be_able_to_type_in_roomName`, { - roomName: getRoomTitle(room) - }), - confirmationText: title, - onPress: () => handleMute(selectedUser, room.rid) - }); - }, - testID: 'action-sheet-mute-user' - }); - } - - // Ignore - if (selectedUser._id !== user.id) { - const { ignored } = room; - const isIgnored = ignored?.includes?.(selectedUser._id); - options.push({ - icon: 'ignore', - title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'), - onPress: () => handleIgnore(selectedUser._id, !isIgnored, room.rid), - testID: 'action-sheet-ignore-user' - }); - } - - // Remove from team - if (editTeamMemberPermission) { - options.push({ - icon: 'logout', - danger: true, - title: I18n.t('Remove_from_Team'), - onPress: () => handleRemoveFromTeam(selectedUser, updateState, room, members), - testID: 'action-sheet-remove-from-team' - }); - } - - // Remove from room - if (removeUserPermission && !room.teamMain) { - options.push({ - icon: 'logout', - title: I18n.t('Remove_from_room'), - danger: true, - onPress: () => { - showConfirmationAlert({ - message: I18n.t('The_user_will_be_removed_from_s', { s: getRoomTitle(room) }), - confirmationText: I18n.t('Yes_remove_user'), - onPress: () => { - handleRemoveUserFromRoom(selectedUser, room, () => - updateState({ - members: members.filter(member => member._id !== selectedUser._id) - }) - ); - } - }); - }, - testID: 'action-sheet-remove-from-room' - }); - } - - showActionSheet({ - options, - hasCancel: true - }); - }; - - const fetchMembers = async () => { - const { members, isLoading, end, room, filter, page, allUsers } = state; - const { t } = room; - - if (isLoading || end) { - return; - } - - const requestId = ++latestSearchRequest.current; - updateState({ isLoading: true }); - try { - const membersResult = await getRoomMembers({ - rid: room.rid, - roomType: t, - type: allUsers ? 'all' : 'online', - filter, - skip: PAGE_SIZE * page, - limit: PAGE_SIZE, - allUsers - }); - - if (requestId !== latestSearchRequest.current) { - return; - } - - const end = membersResult?.length < PAGE_SIZE; - - const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id)); - const newMembers = [...members, ...(membersResultFiltered || [])]; - - updateState({ - members: newMembers, - isLoading: false, - end, - page: page + 1 - }); - } catch (e) { - log(e); - if (requestId === latestSearchRequest.current) { - updateState({ isLoading: false }); - } - } - }; - - return ( - - ( - - onPressUser(item)} - testID={`room-members-view-item-${item.username}`} - /> - - )} - style={styles.list} - keyExtractor={item => item._id} - ItemSeparatorComponent={List.Separator} - ListHeaderComponent={ - <> - - debounceFilterChange(text)} testID='room-members-view-search' /> - - } - ListFooterComponent={() => (state.isLoading ? : null)} - onEndReachedThreshold={0.1} - onEndReached={() => fetchMembers()} - ListEmptyComponent={() => - state.end ? ( - {I18n.t('No_members_found')} - ) : null - } - {...scrollPersistTaps} - /> - - ); + updateState({ + filter: trimmedFilter, + page: 0, + members: [], + end: false, + isLoading: false + }); + }, 500); + + const toggleStatus = (status: boolean) => { + try { + // We only update 'allUsers'. 'filter' remains in state, so the next fetch uses both. + updateState({ members: [], allUsers: status, end: false, page: 0 }); + setHeader(status); + } catch (e) { + log(e); + } + }; + + const setHeader = (allUsers: boolean) => { + navigation.setOptions({ + title: I18n.t('Members'), + headerRight: () => ( + + + showActionSheet({ + options: [ + { + title: I18n.t('Online'), + onPress: () => toggleStatus(false), + right: () => , + testID: 'room-members-view-toggle-status-online' + }, + { + title: I18n.t('All'), + onPress: () => toggleStatus(true), + right: () => , + testID: 'room-members-view-toggle-status-all' + } + ] + }) + } + testID='room-members-view-filter' + /> + + ) + }); + }; + + const getUserDisplayName = (user: TUserModel) => (useRealName ? user.name : user.username) || user.username; + + const onPressUser = (selectedUser: TUserModel) => { + const { room, roomRoles, members } = state; + + const options: TActionSheetOptionsItem[] = [ + { + icon: 'message', + title: I18n.t('Direct_message'), + onPress: () => navToDirectMessage(selectedUser, isMasterDetail) + } + ]; + + // Owner + if (setOwnerPermission) { + const isOwner = fetchRole('owner', selectedUser, roomRoles); + options.push({ + icon: 'shield-check', + title: I18n.t('Owner'), + onPress: () => + handleOwner(selectedUser, !isOwner, getUserDisplayName(selectedUser), room, () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , + testID: 'action-sheet-set-owner' + }); + } + + // Leader + if (setLeaderPermission) { + const isLeader = fetchRole('leader', selectedUser, roomRoles); + options.push({ + icon: 'shield-alt', + title: I18n.t('Leader'), + onPress: () => + handleLeader(selectedUser, !isLeader, room, getUserDisplayName(selectedUser), () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , + testID: 'action-sheet-set-leader' + }); + } + + // Moderator + if (setModeratorPermission) { + const isModerator = fetchRole('moderator', selectedUser, roomRoles); + options.push({ + icon: 'shield', + title: I18n.t('Moderator'), + onPress: () => + handleModerator(selectedUser, !isModerator, room, getUserDisplayName(selectedUser), () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , + testID: 'action-sheet-set-moderator' + }); + } + + if (muteUserPermission) { + const { muted = [], ro: readOnly, unmuted = [] } = room; + let userIsMuted = !!muted.find?.(m => m === selectedUser.username); + let icon: TIconsName = userIsMuted ? 'audio' : 'audio-disabled'; + let title = I18n.t(userIsMuted ? 'Unmute' : 'Mute'); + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.4.0')) { + if (readOnly) { + userIsMuted = !unmuted?.find?.(m => m === selectedUser.username); + } + icon = userIsMuted ? 'message' : 'message-disabled'; + title = I18n.t(userIsMuted ? 'Enable_writing_in_room' : 'Disable_writing_in_room'); + } + selectedUser.muted = !!userIsMuted; + options.push({ + icon, + title, + onPress: () => { + showConfirmationAlert({ + message: I18n.t(`The_user_${userIsMuted ? 'will' : 'wont'}_be_able_to_type_in_roomName`, { + roomName: getRoomTitle(room) + }), + confirmationText: title, + onPress: () => handleMute(selectedUser, room.rid) + }); + }, + testID: 'action-sheet-mute-user' + }); + } + + // Ignore + if (selectedUser._id !== user.id) { + const { ignored } = room; + const isIgnored = ignored?.includes?.(selectedUser._id); + options.push({ + icon: 'ignore', + title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'), + onPress: () => handleIgnore(selectedUser._id, !isIgnored, room.rid), + testID: 'action-sheet-ignore-user' + }); + } + + // Remove from team + if (editTeamMemberPermission) { + options.push({ + icon: 'logout', + danger: true, + title: I18n.t('Remove_from_Team'), + onPress: () => handleRemoveFromTeam(selectedUser, updateState, room, members), + testID: 'action-sheet-remove-from-team' + }); + } + + // Remove from room + if (removeUserPermission && !room.teamMain) { + options.push({ + icon: 'logout', + title: I18n.t('Remove_from_room'), + danger: true, + onPress: () => { + showConfirmationAlert({ + message: I18n.t('The_user_will_be_removed_from_s', { s: getRoomTitle(room) }), + confirmationText: I18n.t('Yes_remove_user'), + onPress: () => { + handleRemoveUserFromRoom(selectedUser, room, () => + updateState({ + members: members.filter(member => member._id !== selectedUser._id) + }) + ); + } + }); + }, + testID: 'action-sheet-remove-from-room' + }); + } + + showActionSheet({ + options, + hasCancel: true + }); + }; + + return ( + + ( + + onPressUser(item)} + testID={`room-members-view-item-${item.username}`} + /> + + )} + style={styles.list} + keyExtractor={item => item._id} + ItemSeparatorComponent={List.Separator} + ListHeaderComponent={ + <> + + debounceFilterChange(text)} testID='room-members-view-search' /> + + } + ListFooterComponent={() => (state.isLoading ? : null)} + onEndReachedThreshold={0.1} + onEndReached={() => fetchMembers()} + ListEmptyComponent={() => + state.end ? ( + {I18n.t('No_members_found')} + ) : null + } + {...scrollPersistTaps} + /> + + ); }; -export default RoomMembersView; +export default RoomMembersView; \ No newline at end of file From d85297958b6dcf3c260826a4c63fb734f758b7b7 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Mon, 26 Jan 2026 19:33:51 +0000 Subject: [PATCH 17/23] chore: format code and fix lint issues [skip ci] --- app/views/RoomMembersView/index.tsx | 820 ++++++++++++++-------------- 1 file changed, 410 insertions(+), 410 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 00f365cc7a2..f10c492a6f3 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -29,432 +29,432 @@ import { type ModalStackParamList } from '../../stacks/MasterDetailStack/types'; import { useTheme } from '../../theme'; import ActionsSection from './components/ActionsSection'; import { - fetchRole, - fetchRoomMembersRoles, - handleLeader, - handleModerator, - handleMute, - handleOwner, - handleRemoveFromTeam, - handleRemoveUserFromRoom, - navToDirectMessage, - type TRoomType + fetchRole, + fetchRoomMembersRoles, + handleLeader, + handleModerator, + handleMute, + handleOwner, + handleRemoveFromTeam, + handleRemoveUserFromRoom, + navToDirectMessage, + type TRoomType } from './helpers'; import styles from './styles'; const PAGE_SIZE = 25; interface IRoomMembersViewState { - isLoading: boolean; - allUsers: boolean; - filtering: string; - members: TUserModel[]; - room: TSubscriptionModel; - end: boolean; - roomRoles?: IGetRoomRoles[]; - filter: string; - page: number; + isLoading: boolean; + allUsers: boolean; + filtering: string; + members: TUserModel[]; + room: TSubscriptionModel; + end: boolean; + roomRoles?: IGetRoomRoles[]; + filter: string; + page: number; } const RightIcon = ({ check, label }: { check: boolean; label: string }) => { - const { colors } = useTheme(); - return ( - - ); + const { colors } = useTheme(); + return ( + + ); }; const RoomMembersView = (): React.ReactElement => { - const { showActionSheet } = useActionSheet(); - const { colors } = useTheme(); - - const { params } = useRoute>(); - const navigation = useNavigation>(); - - const latestSearchRequest = useRef(0); - - const { isMasterDetail, serverVersion, useRealName, user, loading } = useAppSelector( - state => ({ - isMasterDetail: state.app.isMasterDetail, - useRealName: state.settings.UI_Use_Real_Name, - user: getUserSelector(state), - serverVersion: state.server.version, - loading: state.selectedUsers.loading - }), - shallowEqual - ); - - useEffect(() => { - sendLoadingEvent({ visible: loading }); - }, [loading]); - - const [state, updateState] = useReducer( - (state: IRoomMembersViewState, newState: Partial) => ({ ...state, ...newState }), - { - isLoading: false, - allUsers: true, - filtering: '', - members: [], - room: params.room || ({} as TSubscriptionModel), - end: false, - roomRoles: undefined, - filter: '', - page: 0 - } - ); - - const teamPermissions: TSupportedPermissions[] = state.room.teamMain - ? ['edit-team-member', 'view-all-team-channels', 'view-all-teams'] - : []; - - const [ - muteUserPermission, - setLeaderPermission, - setOwnerPermission, - setModeratorPermission, - removeUserPermission, - editTeamMemberPermission, - viewAllTeamChannelsPermission, - viewAllTeamsPermission - ] = usePermissions(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user', ...teamPermissions], params.rid); - - useEffect(() => { - const subscription = params?.room?.observe && params.room.observe().subscribe(changes => updateState({ room: changes })); - setHeader(true); - return () => subscription?.unsubscribe(); - }, []); - - const fetchMembers = useCallback(async () => { - const { members, isLoading, end, room, filter, page, allUsers } = state; - const { t } = room; - - if (isLoading || end) { - return; - } - - const requestId = ++latestSearchRequest.current; - updateState({ isLoading: true }); - - try { - const membersResult = await getRoomMembers({ - rid: room.rid, - roomType: t, - type: allUsers ? 'all' : 'online', - filter, - skip: PAGE_SIZE * page, - limit: PAGE_SIZE, - allUsers - }); - - if (requestId !== latestSearchRequest.current) { - return; - } - - const existingIds = new Set(members.map(m => m._id)); - const membersResultFiltered = membersResult?.filter((member: TUserModel) => !existingIds.has(member._id)); - - // Safety check: if page is 0, we replace the list entirely - const newMembers = page === 0 ? membersResultFiltered : [...members, ...(membersResultFiltered || [])]; - const isEnd = membersResult?.length < PAGE_SIZE; - - updateState({ - members: newMembers, - isLoading: false, - end: isEnd, - page: page + 1 - }); - } catch (e) { - log(e); - if (requestId === latestSearchRequest.current) { - updateState({ isLoading: false }); - } - } - }, [state.members, state.isLoading, state.end, state.room, state.filter, state.page, state.allUsers]); - - useEffect(() => { - const unsubscribe = navigation.addListener('focus', () => { - fetchMembers(); - }); - - return unsubscribe; - }, [navigation, fetchMembers]); - - useEffect(() => { - const fetchRoles = () => { - if (isGroupChat(state.room)) { - return; - } - if ( - muteUserPermission || - setLeaderPermission || - setOwnerPermission || - setModeratorPermission || - removeUserPermission || - editTeamMemberPermission || - viewAllTeamChannelsPermission || - viewAllTeamsPermission - ) { - fetchRoomMembersRoles(state.room.t as any, state.room.rid, updateState); - } - }; - fetchRoles(); - }, [ - muteUserPermission, - setLeaderPermission, - setOwnerPermission, - setModeratorPermission, - removeUserPermission, - editTeamMemberPermission, - viewAllTeamChannelsPermission, - viewAllTeamsPermission, - state.room - ]); - - useEffect(() => { - fetchMembers(); - }, [state.filter, state.allUsers]); - - const debounceFilterChange = useDebounce((text: string) => { - const trimmedFilter = text.trim(); + const { showActionSheet } = useActionSheet(); + const { colors } = useTheme(); + + const { params } = useRoute>(); + const navigation = useNavigation>(); + + const latestSearchRequest = useRef(0); + + const { isMasterDetail, serverVersion, useRealName, user, loading } = useAppSelector( + state => ({ + isMasterDetail: state.app.isMasterDetail, + useRealName: state.settings.UI_Use_Real_Name, + user: getUserSelector(state), + serverVersion: state.server.version, + loading: state.selectedUsers.loading + }), + shallowEqual + ); + + useEffect(() => { + sendLoadingEvent({ visible: loading }); + }, [loading]); + + const [state, updateState] = useReducer( + (state: IRoomMembersViewState, newState: Partial) => ({ ...state, ...newState }), + { + isLoading: false, + allUsers: true, + filtering: '', + members: [], + room: params.room || ({} as TSubscriptionModel), + end: false, + roomRoles: undefined, + filter: '', + page: 0 + } + ); + + const teamPermissions: TSupportedPermissions[] = state.room.teamMain + ? ['edit-team-member', 'view-all-team-channels', 'view-all-teams'] + : []; + + const [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission + ] = usePermissions(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user', ...teamPermissions], params.rid); + + useEffect(() => { + const subscription = params?.room?.observe && params.room.observe().subscribe(changes => updateState({ room: changes })); + setHeader(true); + return () => subscription?.unsubscribe(); + }, []); + + const fetchMembers = useCallback(async () => { + const { members, isLoading, end, room, filter, page, allUsers } = state; + const { t } = room; + + if (isLoading || end) { + return; + } + + const requestId = ++latestSearchRequest.current; + updateState({ isLoading: true }); + + try { + const membersResult = await getRoomMembers({ + rid: room.rid, + roomType: t, + type: allUsers ? 'all' : 'online', + filter, + skip: PAGE_SIZE * page, + limit: PAGE_SIZE, + allUsers + }); + + if (requestId !== latestSearchRequest.current) { + return; + } + + const existingIds = new Set(members.map(m => m._id)); + const membersResultFiltered = membersResult?.filter((member: TUserModel) => !existingIds.has(member._id)); + + // Safety check: if page is 0, we replace the list entirely + const newMembers = page === 0 ? membersResultFiltered : [...members, ...(membersResultFiltered || [])]; + const isEnd = membersResult?.length < PAGE_SIZE; + + updateState({ + members: newMembers, + isLoading: false, + end: isEnd, + page: page + 1 + }); + } catch (e) { + log(e); + if (requestId === latestSearchRequest.current) { + updateState({ isLoading: false }); + } + } + }, [state.members, state.isLoading, state.end, state.room, state.filter, state.page, state.allUsers]); + + useEffect(() => { + const unsubscribe = navigation.addListener('focus', () => { + fetchMembers(); + }); + + return unsubscribe; + }, [navigation, fetchMembers]); + + useEffect(() => { + const fetchRoles = () => { + if (isGroupChat(state.room)) { + return; + } + if ( + muteUserPermission || + setLeaderPermission || + setOwnerPermission || + setModeratorPermission || + removeUserPermission || + editTeamMemberPermission || + viewAllTeamChannelsPermission || + viewAllTeamsPermission + ) { + fetchRoomMembersRoles(state.room.t as any, state.room.rid, updateState); + } + }; + fetchRoles(); + }, [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission, + state.room + ]); + + useEffect(() => { + fetchMembers(); + }, [state.filter, state.allUsers]); + + const debounceFilterChange = useDebounce((text: string) => { + const trimmedFilter = text.trim(); if (!trimmedFilter) { latestSearchRequest.current += 1; } - updateState({ - filter: trimmedFilter, - page: 0, - members: [], - end: false, - isLoading: false - }); - }, 500); - - const toggleStatus = (status: boolean) => { - try { - // We only update 'allUsers'. 'filter' remains in state, so the next fetch uses both. - updateState({ members: [], allUsers: status, end: false, page: 0 }); - setHeader(status); - } catch (e) { - log(e); - } - }; - - const setHeader = (allUsers: boolean) => { - navigation.setOptions({ - title: I18n.t('Members'), - headerRight: () => ( - - - showActionSheet({ - options: [ - { - title: I18n.t('Online'), - onPress: () => toggleStatus(false), - right: () => , - testID: 'room-members-view-toggle-status-online' - }, - { - title: I18n.t('All'), - onPress: () => toggleStatus(true), - right: () => , - testID: 'room-members-view-toggle-status-all' - } - ] - }) - } - testID='room-members-view-filter' - /> - - ) - }); - }; - - const getUserDisplayName = (user: TUserModel) => (useRealName ? user.name : user.username) || user.username; - - const onPressUser = (selectedUser: TUserModel) => { - const { room, roomRoles, members } = state; - - const options: TActionSheetOptionsItem[] = [ - { - icon: 'message', - title: I18n.t('Direct_message'), - onPress: () => navToDirectMessage(selectedUser, isMasterDetail) - } - ]; - - // Owner - if (setOwnerPermission) { - const isOwner = fetchRole('owner', selectedUser, roomRoles); - options.push({ - icon: 'shield-check', - title: I18n.t('Owner'), - onPress: () => - handleOwner(selectedUser, !isOwner, getUserDisplayName(selectedUser), room, () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) - ), - right: () => , - testID: 'action-sheet-set-owner' - }); - } - - // Leader - if (setLeaderPermission) { - const isLeader = fetchRole('leader', selectedUser, roomRoles); - options.push({ - icon: 'shield-alt', - title: I18n.t('Leader'), - onPress: () => - handleLeader(selectedUser, !isLeader, room, getUserDisplayName(selectedUser), () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) - ), - right: () => , - testID: 'action-sheet-set-leader' - }); - } - - // Moderator - if (setModeratorPermission) { - const isModerator = fetchRole('moderator', selectedUser, roomRoles); - options.push({ - icon: 'shield', - title: I18n.t('Moderator'), - onPress: () => - handleModerator(selectedUser, !isModerator, room, getUserDisplayName(selectedUser), () => - fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) - ), - right: () => , - testID: 'action-sheet-set-moderator' - }); - } - - if (muteUserPermission) { - const { muted = [], ro: readOnly, unmuted = [] } = room; - let userIsMuted = !!muted.find?.(m => m === selectedUser.username); - let icon: TIconsName = userIsMuted ? 'audio' : 'audio-disabled'; - let title = I18n.t(userIsMuted ? 'Unmute' : 'Mute'); - if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.4.0')) { - if (readOnly) { - userIsMuted = !unmuted?.find?.(m => m === selectedUser.username); - } - icon = userIsMuted ? 'message' : 'message-disabled'; - title = I18n.t(userIsMuted ? 'Enable_writing_in_room' : 'Disable_writing_in_room'); - } - selectedUser.muted = !!userIsMuted; - options.push({ - icon, - title, - onPress: () => { - showConfirmationAlert({ - message: I18n.t(`The_user_${userIsMuted ? 'will' : 'wont'}_be_able_to_type_in_roomName`, { - roomName: getRoomTitle(room) - }), - confirmationText: title, - onPress: () => handleMute(selectedUser, room.rid) - }); - }, - testID: 'action-sheet-mute-user' - }); - } - - // Ignore - if (selectedUser._id !== user.id) { - const { ignored } = room; - const isIgnored = ignored?.includes?.(selectedUser._id); - options.push({ - icon: 'ignore', - title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'), - onPress: () => handleIgnore(selectedUser._id, !isIgnored, room.rid), - testID: 'action-sheet-ignore-user' - }); - } - - // Remove from team - if (editTeamMemberPermission) { - options.push({ - icon: 'logout', - danger: true, - title: I18n.t('Remove_from_Team'), - onPress: () => handleRemoveFromTeam(selectedUser, updateState, room, members), - testID: 'action-sheet-remove-from-team' - }); - } - - // Remove from room - if (removeUserPermission && !room.teamMain) { - options.push({ - icon: 'logout', - title: I18n.t('Remove_from_room'), - danger: true, - onPress: () => { - showConfirmationAlert({ - message: I18n.t('The_user_will_be_removed_from_s', { s: getRoomTitle(room) }), - confirmationText: I18n.t('Yes_remove_user'), - onPress: () => { - handleRemoveUserFromRoom(selectedUser, room, () => - updateState({ - members: members.filter(member => member._id !== selectedUser._id) - }) - ); - } - }); - }, - testID: 'action-sheet-remove-from-room' - }); - } - - showActionSheet({ - options, - hasCancel: true - }); - }; - - return ( - - ( - - onPressUser(item)} - testID={`room-members-view-item-${item.username}`} - /> - - )} - style={styles.list} - keyExtractor={item => item._id} - ItemSeparatorComponent={List.Separator} - ListHeaderComponent={ - <> - - debounceFilterChange(text)} testID='room-members-view-search' /> - - } - ListFooterComponent={() => (state.isLoading ? : null)} - onEndReachedThreshold={0.1} - onEndReached={() => fetchMembers()} - ListEmptyComponent={() => - state.end ? ( - {I18n.t('No_members_found')} - ) : null - } - {...scrollPersistTaps} - /> - - ); + updateState({ + filter: trimmedFilter, + page: 0, + members: [], + end: false, + isLoading: false + }); + }, 500); + + const toggleStatus = (status: boolean) => { + try { + // We only update 'allUsers'. 'filter' remains in state, so the next fetch uses both. + updateState({ members: [], allUsers: status, end: false, page: 0 }); + setHeader(status); + } catch (e) { + log(e); + } + }; + + const setHeader = (allUsers: boolean) => { + navigation.setOptions({ + title: I18n.t('Members'), + headerRight: () => ( + + + showActionSheet({ + options: [ + { + title: I18n.t('Online'), + onPress: () => toggleStatus(false), + right: () => , + testID: 'room-members-view-toggle-status-online' + }, + { + title: I18n.t('All'), + onPress: () => toggleStatus(true), + right: () => , + testID: 'room-members-view-toggle-status-all' + } + ] + }) + } + testID='room-members-view-filter' + /> + + ) + }); + }; + + const getUserDisplayName = (user: TUserModel) => (useRealName ? user.name : user.username) || user.username; + + const onPressUser = (selectedUser: TUserModel) => { + const { room, roomRoles, members } = state; + + const options: TActionSheetOptionsItem[] = [ + { + icon: 'message', + title: I18n.t('Direct_message'), + onPress: () => navToDirectMessage(selectedUser, isMasterDetail) + } + ]; + + // Owner + if (setOwnerPermission) { + const isOwner = fetchRole('owner', selectedUser, roomRoles); + options.push({ + icon: 'shield-check', + title: I18n.t('Owner'), + onPress: () => + handleOwner(selectedUser, !isOwner, getUserDisplayName(selectedUser), room, () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , + testID: 'action-sheet-set-owner' + }); + } + + // Leader + if (setLeaderPermission) { + const isLeader = fetchRole('leader', selectedUser, roomRoles); + options.push({ + icon: 'shield-alt', + title: I18n.t('Leader'), + onPress: () => + handleLeader(selectedUser, !isLeader, room, getUserDisplayName(selectedUser), () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , + testID: 'action-sheet-set-leader' + }); + } + + // Moderator + if (setModeratorPermission) { + const isModerator = fetchRole('moderator', selectedUser, roomRoles); + options.push({ + icon: 'shield', + title: I18n.t('Moderator'), + onPress: () => + handleModerator(selectedUser, !isModerator, room, getUserDisplayName(selectedUser), () => + fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState) + ), + right: () => , + testID: 'action-sheet-set-moderator' + }); + } + + if (muteUserPermission) { + const { muted = [], ro: readOnly, unmuted = [] } = room; + let userIsMuted = !!muted.find?.(m => m === selectedUser.username); + let icon: TIconsName = userIsMuted ? 'audio' : 'audio-disabled'; + let title = I18n.t(userIsMuted ? 'Unmute' : 'Mute'); + if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.4.0')) { + if (readOnly) { + userIsMuted = !unmuted?.find?.(m => m === selectedUser.username); + } + icon = userIsMuted ? 'message' : 'message-disabled'; + title = I18n.t(userIsMuted ? 'Enable_writing_in_room' : 'Disable_writing_in_room'); + } + selectedUser.muted = !!userIsMuted; + options.push({ + icon, + title, + onPress: () => { + showConfirmationAlert({ + message: I18n.t(`The_user_${userIsMuted ? 'will' : 'wont'}_be_able_to_type_in_roomName`, { + roomName: getRoomTitle(room) + }), + confirmationText: title, + onPress: () => handleMute(selectedUser, room.rid) + }); + }, + testID: 'action-sheet-mute-user' + }); + } + + // Ignore + if (selectedUser._id !== user.id) { + const { ignored } = room; + const isIgnored = ignored?.includes?.(selectedUser._id); + options.push({ + icon: 'ignore', + title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'), + onPress: () => handleIgnore(selectedUser._id, !isIgnored, room.rid), + testID: 'action-sheet-ignore-user' + }); + } + + // Remove from team + if (editTeamMemberPermission) { + options.push({ + icon: 'logout', + danger: true, + title: I18n.t('Remove_from_Team'), + onPress: () => handleRemoveFromTeam(selectedUser, updateState, room, members), + testID: 'action-sheet-remove-from-team' + }); + } + + // Remove from room + if (removeUserPermission && !room.teamMain) { + options.push({ + icon: 'logout', + title: I18n.t('Remove_from_room'), + danger: true, + onPress: () => { + showConfirmationAlert({ + message: I18n.t('The_user_will_be_removed_from_s', { s: getRoomTitle(room) }), + confirmationText: I18n.t('Yes_remove_user'), + onPress: () => { + handleRemoveUserFromRoom(selectedUser, room, () => + updateState({ + members: members.filter(member => member._id !== selectedUser._id) + }) + ); + } + }); + }, + testID: 'action-sheet-remove-from-room' + }); + } + + showActionSheet({ + options, + hasCancel: true + }); + }; + + return ( + + ( + + onPressUser(item)} + testID={`room-members-view-item-${item.username}`} + /> + + )} + style={styles.list} + keyExtractor={item => item._id} + ItemSeparatorComponent={List.Separator} + ListHeaderComponent={ + <> + + debounceFilterChange(text)} testID='room-members-view-search' /> + + } + ListFooterComponent={() => (state.isLoading ? : null)} + onEndReachedThreshold={0.1} + onEndReached={() => fetchMembers()} + ListEmptyComponent={() => + state.end ? ( + {I18n.t('No_members_found')} + ) : null + } + {...scrollPersistTaps} + /> + + ); }; -export default RoomMembersView; \ No newline at end of file +export default RoomMembersView; From 8a0316b7c1c2ce3a10eb4cdd4951045dca81442f Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Mon, 26 Jan 2026 16:35:14 -0300 Subject: [PATCH 18/23] chore: code improvements --- .maestro/tests/room/search-member.yaml | 9 ++++++ app/views/RoomMembersView/index.tsx | 41 +++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/.maestro/tests/room/search-member.yaml b/.maestro/tests/room/search-member.yaml index 5f710e9e840..e79789c1c29 100644 --- a/.maestro/tests/room/search-member.yaml +++ b/.maestro/tests/room/search-member.yaml @@ -80,3 +80,12 @@ tags: visible: id: 'room-members-view-item-${output.secondUser.username}' timeout: 60000 + +# Verify "No members found" message appears correctly when search returns no results +- tapOn: + id: room-members-view-search +- inputText: nonexistentuser12345 +- extendedWaitUntil: + visible: + text: 'No members found' + timeout: 60000 \ No newline at end of file diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index f10c492a6f3..297bb644c5d 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -128,9 +128,27 @@ const RoomMembersView = (): React.ReactElement => { return () => subscription?.unsubscribe(); }, []); - const fetchMembers = useCallback(async () => { - const { members, isLoading, end, room, filter, page, allUsers } = state; - const { t } = room; + const fetchRoles = () => { + if (isGroupChat(state.room)) { + return; + } + if ( + muteUserPermission || + setLeaderPermission || + setOwnerPermission || + setModeratorPermission || + removeUserPermission || + editTeamMemberPermission || + viewAllTeamChannelsPermission || + viewAllTeamsPermission + ) { + fetchRoomMembersRoles(state.room.t as any, state.room.rid, updateState); + } + }; + + const fetchMembers = useCallback(async () => { + const { members, isLoading, end, room, filter, page, allUsers } = state; + const { t } = room; if (isLoading || end) { return; @@ -181,7 +199,22 @@ const RoomMembersView = (): React.ReactElement => { }); return unsubscribe; - }, [navigation, fetchMembers]); + }, [navigation]); + + useEffect(() => { + + fetchRoles(); + }, [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission, + state.room + ]); useEffect(() => { const fetchRoles = () => { From 2cf2053aedba2ffdb6a2111ebde0f5dc9df60d5e Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Mon, 26 Jan 2026 21:40:05 +0000 Subject: [PATCH 19/23] chore: format code and fix lint issues [skip ci] --- .maestro/tests/room/search-member.yaml | 2 +- app/views/RoomMembersView/index.tsx | 33 +++++++++++++------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.maestro/tests/room/search-member.yaml b/.maestro/tests/room/search-member.yaml index e79789c1c29..6d6bf982b37 100644 --- a/.maestro/tests/room/search-member.yaml +++ b/.maestro/tests/room/search-member.yaml @@ -88,4 +88,4 @@ tags: - extendedWaitUntil: visible: text: 'No members found' - timeout: 60000 \ No newline at end of file + timeout: 60000 diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 297bb644c5d..68afb3a0497 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -146,9 +146,9 @@ const RoomMembersView = (): React.ReactElement => { } }; - const fetchMembers = useCallback(async () => { - const { members, isLoading, end, room, filter, page, allUsers } = state; - const { t } = room; + const fetchMembers = useCallback(async () => { + const { members, isLoading, end, room, filter, page, allUsers } = state; + const { t } = room; if (isLoading || end) { return; @@ -201,20 +201,19 @@ const RoomMembersView = (): React.ReactElement => { return unsubscribe; }, [navigation]); - useEffect(() => { - - fetchRoles(); - }, [ - muteUserPermission, - setLeaderPermission, - setOwnerPermission, - setModeratorPermission, - removeUserPermission, - editTeamMemberPermission, - viewAllTeamChannelsPermission, - viewAllTeamsPermission, - state.room - ]); + useEffect(() => { + fetchRoles(); + }, [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission, + state.room + ]); useEffect(() => { const fetchRoles = () => { From 806e79ea9466c0772f4e84ce8e37cf3a53e75190 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Tue, 27 Jan 2026 13:13:41 -0300 Subject: [PATCH 20/23] fix: remove duplicated fetch roles and remove members from state of useCallback --- app/views/RoomMembersView/index.tsx | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 68afb3a0497..2a011580398 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -191,7 +191,7 @@ const RoomMembersView = (): React.ReactElement => { updateState({ isLoading: false }); } } - }, [state.members, state.isLoading, state.end, state.room, state.filter, state.page, state.allUsers]); + }, [state.isLoading, state.end, state.room, state.filter, state.page, state.allUsers]); useEffect(() => { const unsubscribe = navigation.addListener('focus', () => { @@ -216,23 +216,6 @@ const RoomMembersView = (): React.ReactElement => { ]); useEffect(() => { - const fetchRoles = () => { - if (isGroupChat(state.room)) { - return; - } - if ( - muteUserPermission || - setLeaderPermission || - setOwnerPermission || - setModeratorPermission || - removeUserPermission || - editTeamMemberPermission || - viewAllTeamChannelsPermission || - viewAllTeamsPermission - ) { - fetchRoomMembersRoles(state.room.t as any, state.room.rid, updateState); - } - }; fetchRoles(); }, [ muteUserPermission, From f207cb7afc924a2c711a82246d4a6b007c9b1e9a Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Tue, 27 Jan 2026 14:51:33 -0300 Subject: [PATCH 21/23] fix: remove duplicated useEffect and use primitive values in fetchRoles useEffect dependencies --- app/views/RoomMembersView/index.tsx | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 2a011580398..7fbbdc08db0 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -212,22 +212,11 @@ const RoomMembersView = (): React.ReactElement => { editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission, - state.room + state.room?.rid, + state.room?.t ]); - useEffect(() => { - fetchRoles(); - }, [ - muteUserPermission, - setLeaderPermission, - setOwnerPermission, - setModeratorPermission, - removeUserPermission, - editTeamMemberPermission, - viewAllTeamChannelsPermission, - viewAllTeamsPermission, - state.room - ]); + useEffect(() => { fetchMembers(); From 33265f04b64f80d3029b0aa972888fea79ed18e3 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Tue, 27 Jan 2026 17:53:15 +0000 Subject: [PATCH 22/23] chore: format code and fix lint issues [skip ci] --- app/views/RoomMembersView/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 7fbbdc08db0..7d1beb013b2 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -216,8 +216,6 @@ const RoomMembersView = (): React.ReactElement => { state.room?.t ]); - - useEffect(() => { fetchMembers(); }, [state.filter, state.allUsers]); From 503bc9fcdbd489a2ea18452d280a80db92bf5546 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Tue, 27 Jan 2026 15:56:10 -0300 Subject: [PATCH 23/23] fix: remove array from useEffect dependencies --- app/views/RoomMembersView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 7d1beb013b2..a285fe5f148 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -191,7 +191,7 @@ const RoomMembersView = (): React.ReactElement => { updateState({ isLoading: false }); } } - }, [state.isLoading, state.end, state.room, state.filter, state.page, state.allUsers]); + }, [state.isLoading, state.end, state.room.t, state.filter, state.page, state.allUsers]); useEffect(() => { const unsubscribe = navigation.addListener('focus', () => {