Skip to content

Commit 91c4ae2

Browse files
committed
fix: channel list stuck in refreshing state
1 parent 8c6aa79 commit 91c4ae2

2 files changed

Lines changed: 63 additions & 5 deletions

File tree

package/src/components/ChannelList/__tests__/ChannelList.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import dispatchChannelDeletedEvent from '../../../mock-builders/event/channelDel
2020
import dispatchChannelHiddenEvent from '../../../mock-builders/event/channelHidden';
2121
import dispatchChannelTruncatedEvent from '../../../mock-builders/event/channelTruncated';
2222
import dispatchChannelUpdatedEvent from '../../../mock-builders/event/channelUpdated';
23+
import dispatchConnectionChangedEvent from '../../../mock-builders/event/connectionChanged';
2324
import dispatchConnectionRecoveredEvent from '../../../mock-builders/event/connectionRecovered';
2425
import dispatchMessageNewEvent from '../../../mock-builders/event/messageNew';
2526
import dispatchNotificationAddedToChannelEvent from '../../../mock-builders/event/notificationAddedToChannel';
@@ -61,6 +62,11 @@ const ChannelListComponent = (props) => {
6162
);
6263
};
6364

65+
const RefreshingProbe = () => {
66+
const { refreshing } = useChannelsContext();
67+
return <Text testID='refreshing'>{`${refreshing}`}</Text>;
68+
};
69+
6470
class DeferredPromise {
6571
constructor() {
6672
this.promise = new Promise((resolve, reject) => {
@@ -670,6 +676,55 @@ describe('ChannelList', () => {
670676
});
671677
});
672678

679+
describe('connection.changed', () => {
680+
it('should keep background reconnection refreshes debounced and out of pull-to-refresh UI', async () => {
681+
useMockedApis(chatClient, [queryChannelsApi([testChannel1])]);
682+
const deferredPromise = new DeferredPromise();
683+
const dateNowSpy = jest.spyOn(Date, 'now');
684+
dateNowSpy.mockReturnValueOnce(0);
685+
dateNowSpy.mockReturnValue(6000);
686+
687+
render(
688+
<Chat client={chatClient}>
689+
<ChannelList {...props} List={RefreshingProbe} />
690+
</Chat>,
691+
);
692+
693+
await waitFor(() => {
694+
expect(screen.getByTestId('refreshing').children[0]).toBe('false');
695+
});
696+
697+
const queryChannelsSpy = jest
698+
.spyOn(chatClient, 'queryChannels')
699+
.mockImplementation(() => deferredPromise.promise);
700+
701+
await act(async () => {
702+
dispatchConnectionChangedEvent(chatClient, false);
703+
dispatchConnectionChangedEvent(chatClient, true);
704+
await Promise.resolve();
705+
});
706+
707+
await waitFor(() => {
708+
expect(queryChannelsSpy).toHaveBeenCalledTimes(1);
709+
});
710+
711+
await act(async () => {
712+
dispatchConnectionChangedEvent(chatClient, true);
713+
await Promise.resolve();
714+
});
715+
716+
expect(queryChannelsSpy).toHaveBeenCalledTimes(1);
717+
expect(screen.getByTestId('refreshing').children[0]).toBe('false');
718+
719+
await act(async () => {
720+
deferredPromise.resolve([generateChannel({ id: testChannel1.channel.id })]);
721+
await deferredPromise.promise;
722+
});
723+
724+
dateNowSpy.mockRestore();
725+
});
726+
});
727+
673728
describe('channel.truncated', () => {
674729
it('should call the `onChannelTruncated` function prop, if provided', async () => {
675730
useMockedApis(chatClient, [queryChannelsApi([testChannel1])]);

package/src/components/ChannelList/hooks/usePaginatedChannels.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type Parameters = {
2424

2525
const RETRY_INTERVAL_IN_MS = 5000;
2626

27-
type QueryType = 'queryLocalDB' | 'reload' | 'refresh' | 'loadChannels';
27+
type QueryType = 'queryLocalDB' | 'reload' | 'refresh' | 'loadChannels' | 'backgroundRefresh';
2828

2929
export type QueryChannels = (queryType?: QueryType, retryCount?: number) => Promise<void>;
3030

@@ -68,6 +68,7 @@ export const usePaginatedChannels = ({
6868
const hasUpdatedData =
6969
queryType === 'loadChannels' ||
7070
queryType === 'refresh' ||
71+
queryType === 'backgroundRefresh' ||
7172
[
7273
JSON.stringify(filtersRef.current) !== JSON.stringify(filters),
7374
JSON.stringify(sortRef.current) !== JSON.stringify(sort),
@@ -129,15 +130,15 @@ export const usePaginatedChannels = ({
129130
setActiveQueryType(null);
130131
};
131132

132-
const refreshList = async () => {
133+
const refreshList = async ({ isBackground = false }: { isBackground?: boolean } = {}) => {
133134
const now = Date.now();
134135
// Only allow pull-to-refresh 5 seconds after last successful refresh.
135136
if (now - lastRefresh.current < RETRY_INTERVAL_IN_MS && error === undefined) {
136137
return;
137138
}
138139

139140
lastRefresh.current = Date.now();
140-
await queryChannels('refresh');
141+
await queryChannels(isBackground ? 'backgroundRefresh' : 'refresh');
141142
};
142143

143144
const reloadList = async () => {
@@ -167,7 +168,9 @@ export const usePaginatedChannels = ({
167168
'connection.changed',
168169
async (event) => {
169170
if (event.online) {
170-
await refreshList();
171+
// Reconnection refreshes should stay silent, but still share the same debounce
172+
// path as pull-to-refresh.
173+
await refreshList({ isBackground: true });
171174
}
172175
},
173176
);
@@ -195,7 +198,7 @@ export const usePaginatedChannels = ({
195198
loadingNextPage: pagination?.isLoadingNext,
196199
loadNextPage: channelManager.loadNext,
197200
refreshing: activeQueryType === 'refresh',
198-
refreshList,
201+
refreshList: () => refreshList(),
199202
reloadList,
200203
staticChannelsActive,
201204
};

0 commit comments

Comments
 (0)