From db25a335f42f8c4874e04e3d06aa5872e745e90f Mon Sep 17 00:00:00 2001 From: Deepak Bhagat Date: Sun, 9 Aug 2026 13:26:50 +0530 Subject: [PATCH 1/5] fix: stop useSubscriptions re-querying on connection state changes useSubscriptions selected the entire state.server object and listed it in the useEffect dependency array. state.server holds connection-state fields (connecting/loading/connected) that change on every transition, producing a new object reference and re-running the effect, which re-subscribes and re-queries WatermelonDB on each connection change and makes the room list flicker. The selected server value was never used inside the effect, so remove the unused selector and its dependency. The query now only re-runs when the subscription-relevant preferences actually change. Fixes #7437 Signed-off-by: Deepak Bhagat --- .../hooks/useSubscriptions.test.tsx | 76 +++++++++++++++++++ .../RoomsListView/hooks/useSubscriptions.ts | 3 +- 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 app/views/RoomsListView/hooks/useSubscriptions.test.tsx diff --git a/app/views/RoomsListView/hooks/useSubscriptions.test.tsx b/app/views/RoomsListView/hooks/useSubscriptions.test.tsx new file mode 100644 index 00000000000..56251de40a5 --- /dev/null +++ b/app/views/RoomsListView/hooks/useSubscriptions.test.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { render, act } from '@testing-library/react-native'; +import { Provider } from 'react-redux'; +import { createStore } from 'redux'; + +import { useSubscriptions } from './useSubscriptions'; +import database from '../../lib/database'; + +let queryCallCount = 0; + +jest.mock('../../lib/database', () => ({ + __esModule: true, + default: { + active: { + get: () => ({ + query: () => ({ + observeWithColumns: () => { + queryCallCount += 1; + return { + subscribe: (cb: (data: any[]) => void) => { + cb([]); + return { unsubscribe: jest.fn() }; + } + }; + } + }) + }) + } + } +})); + +const baseState = { + server: { connecting: false, loading: false, connected: true }, + settings: { UI_Use_Real_Name: false }, + sortPreferences: { sortBy: 'activity', showUnread: false, showFavorites: false, groupByType: false }, + login: { user: { roles: [] } } +}; + +const rootReducer = (state: any = baseState, action: any) => + action.type === 'UPDATE_SERVER' ? { ...state, server: action.server } : state; + +const Harness = () => { + useSubscriptions(); + return null; +}; + +describe('useSubscriptions', () => { + it('does not re-query on connection state changes', () => { + const store = createStore(rootReducer, baseState); + + render( + + + + ); + + const initialCalls = queryCallCount; + expect(initialCalls).toBeGreaterThanOrEqual(1); + + act(() => { + store.dispatch({ + type: 'UPDATE_SERVER', + server: { connecting: true, loading: false, connected: false } + }); + }); + + act(() => { + store.dispatch({ + type: 'UPDATE_SERVER', + server: { connecting: false, loading: true, connected: false } + }); + }); + + expect(queryCallCount).toBe(initialCalls); + }); +}); diff --git a/app/views/RoomsListView/hooks/useSubscriptions.ts b/app/views/RoomsListView/hooks/useSubscriptions.ts index 485649f35c4..8e95c74cba4 100644 --- a/app/views/RoomsListView/hooks/useSubscriptions.ts +++ b/app/views/RoomsListView/hooks/useSubscriptions.ts @@ -39,7 +39,6 @@ export const useSubscriptions = () => { 'use memo'; const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name); - const server = useAppSelector(state => state.server); const subscriptionRef = useRef(null); const [subscriptions, setSubscriptions] = useState([]); const [loading, setLoading] = useState(true); @@ -124,7 +123,7 @@ export const useSubscriptions = () => { return () => { subscriptionRef.current?.unsubscribe(); }; - }, [isGrouping, sortBy, useRealName, showUnread, showFavorites, groupByType, roles, server]); + }, [isGrouping, sortBy, useRealName, showUnread, showFavorites, groupByType, roles]); return { subscriptions, From d67eb201529e38cd7cd5e4a8b3b20cabdd2d23be Mon Sep 17 00:00:00 2001 From: Deepak Bhagat Date: Sun, 9 Aug 2026 16:01:23 +0530 Subject: [PATCH 2/5] test: remove unused database import in useSubscriptions test Removes the unused database import flagged by Oxlint (no-unused-vars and import/extensions). jest.mock does not require a local binding. Signed-off-by: Deepak Bhagat --- app/views/RoomsListView/hooks/useSubscriptions.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/RoomsListView/hooks/useSubscriptions.test.tsx b/app/views/RoomsListView/hooks/useSubscriptions.test.tsx index 56251de40a5..3e7a68f40e2 100644 --- a/app/views/RoomsListView/hooks/useSubscriptions.test.tsx +++ b/app/views/RoomsListView/hooks/useSubscriptions.test.tsx @@ -4,7 +4,6 @@ import { Provider } from 'react-redux'; import { createStore } from 'redux'; import { useSubscriptions } from './useSubscriptions'; -import database from '../../lib/database'; let queryCallCount = 0; From 279303cde6fb2c58b9b449034fac5d65426fb198 Mon Sep 17 00:00:00 2001 From: Deepak Bhagat Date: Sun, 9 Aug 2026 16:13:02 +0530 Subject: [PATCH 3/5] fix: depend on active server identity, not whole state.server object Address CodeRabbit review on #7559: - depend on state.server.server (the active server URL) instead of the entire state.server object. Connection-state transitions (connecting/loading) no longer re-run the effect, fixing the room-list flicker, while an actual server switch (which changes the URL and the underlying database.active) still re-subscribes correctly. - make the regression test async: wait for the initial subscription setup after render and wait for async work to settle after each dispatched connection-state change before asserting the query count is unchanged. Signed-off-by: Deepak Bhagat --- app/views/RoomsListView/hooks/useSubscriptions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/RoomsListView/hooks/useSubscriptions.ts b/app/views/RoomsListView/hooks/useSubscriptions.ts index 8e95c74cba4..db6f6b00d26 100644 --- a/app/views/RoomsListView/hooks/useSubscriptions.ts +++ b/app/views/RoomsListView/hooks/useSubscriptions.ts @@ -39,6 +39,7 @@ export const useSubscriptions = () => { 'use memo'; const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name); + const server = useAppSelector(state => state.server.server); const subscriptionRef = useRef(null); const [subscriptions, setSubscriptions] = useState([]); const [loading, setLoading] = useState(true); @@ -123,7 +124,7 @@ export const useSubscriptions = () => { return () => { subscriptionRef.current?.unsubscribe(); }; - }, [isGrouping, sortBy, useRealName, showUnread, showFavorites, groupByType, roles]); + }, [isGrouping, sortBy, useRealName, showUnread, showFavorites, groupByType, roles, server]); return { subscriptions, From ecf8ab40d83e32201122e2e7f86158f24f8728e6 Mon Sep 17 00:00:00 2001 From: Deepak Bhagat Date: Sun, 9 Aug 2026 16:13:49 +0530 Subject: [PATCH 4/5] test: make useSubscriptions regression test async and server-identity aware Wait for the initial async subscription setup after render and for async work to settle after each dispatched connection-state change before asserting the query count is unchanged. Use state.server.server (active server URL) in the mock store to match the updated selector. Signed-off-by: Deepak Bhagat --- .../hooks/useSubscriptions.test.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/views/RoomsListView/hooks/useSubscriptions.test.tsx b/app/views/RoomsListView/hooks/useSubscriptions.test.tsx index 3e7a68f40e2..734fe1c3775 100644 --- a/app/views/RoomsListView/hooks/useSubscriptions.test.tsx +++ b/app/views/RoomsListView/hooks/useSubscriptions.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, act } from '@testing-library/react-native'; +import { render, act, waitFor } from '@testing-library/react-native'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; @@ -29,7 +29,7 @@ jest.mock('../../lib/database', () => ({ })); const baseState = { - server: { connecting: false, loading: false, connected: true }, + server: { server: 'https://open.rocket.chat', connecting: false, loading: false, connected: true }, settings: { UI_Use_Real_Name: false }, sortPreferences: { sortBy: 'activity', showUnread: false, showFavorites: false, groupByType: false }, login: { user: { roles: [] } } @@ -44,7 +44,7 @@ const Harness = () => { }; describe('useSubscriptions', () => { - it('does not re-query on connection state changes', () => { + it('does not re-query on connection state changes', async () => { const store = createStore(rootReducer, baseState); render( @@ -53,23 +53,27 @@ describe('useSubscriptions', () => { ); + await waitFor(() => { + expect(queryCallCount).toBeGreaterThanOrEqual(1); + }); const initialCalls = queryCallCount; - expect(initialCalls).toBeGreaterThanOrEqual(1); - act(() => { + await act(async () => { store.dispatch({ type: 'UPDATE_SERVER', - server: { connecting: true, loading: false, connected: false } + server: { server: 'https://open.rocket.chat', connecting: true, loading: false, connected: false } }); }); - act(() => { + await act(async () => { store.dispatch({ type: 'UPDATE_SERVER', - server: { connecting: false, loading: true, connected: false } + server: { server: 'https://open.rocket.chat', connecting: false, loading: true, connected: false } }); }); - expect(queryCallCount).toBe(initialCalls); + await waitFor(() => { + expect(queryCallCount).toBe(initialCalls); + }); }); }); From abb08dcc599bfdc1be973b317a3b2dca30cb8c0f Mon Sep 17 00:00:00 2001 From: Deepak Bhagat Date: Sun, 9 Aug 2026 22:06:44 +0530 Subject: [PATCH 5/5] test: fix database mock import path in useSubscriptions test Use the correct relative path (../../../lib/database) matching the source import so jest can resolve the module mock. Signed-off-by: Deepak Bhagat --- app/views/RoomsListView/hooks/useSubscriptions.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/RoomsListView/hooks/useSubscriptions.test.tsx b/app/views/RoomsListView/hooks/useSubscriptions.test.tsx index 734fe1c3775..d28c15cdc77 100644 --- a/app/views/RoomsListView/hooks/useSubscriptions.test.tsx +++ b/app/views/RoomsListView/hooks/useSubscriptions.test.tsx @@ -7,7 +7,7 @@ import { useSubscriptions } from './useSubscriptions'; let queryCallCount = 0; -jest.mock('../../lib/database', () => ({ +jest.mock('../../../lib/database', () => ({ __esModule: true, default: { active: {