Skip to content

Commit 1474278

Browse files
authored
feat: add reconnectOnIdle option to ChannelProvider (#690)
Addresses https://sendbird.atlassian.net/browse/AC-200 Added an option to the ChannelProvider: `reconnectOnIdle`(default: true), which prevents data refresh in the background.
1 parent 0451377 commit 1474278

File tree

7 files changed

+92
-6
lines changed

7 files changed

+92
-6
lines changed

scripts/index_d_ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,7 @@ declare module "SendbirdUIKitGlobal" {
584584
onMessageAnimated?: () => void;
585585
onMessageHighlighted?: () => void;
586586
scrollBehavior?: 'smooth' | 'auto';
587+
reconnectOnIdle?: boolean;
587588
};
588589

589590
export interface ChannelUIProps {

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ type ChannelContextProps = {
746746
disableUserProfile?: boolean;
747747
disableMarkAsRead?: boolean;
748748
scrollBehavior?: 'smooth' | 'auto';
749+
reconnectOnIdle?: boolean;
749750
};
750751

751752
interface ChannelUIProps {

src/modules/Channel/context/ChannelProvider.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export type ChannelContextProps = {
9696
onMessageAnimated?: () => void;
9797
onMessageHighlighted?: () => void;
9898
scrollBehavior?: 'smooth' | 'auto';
99+
reconnectOnIdle?: boolean;
99100
};
100101

101102
interface MessageStoreInterface {
@@ -188,6 +189,7 @@ const ChannelProvider: React.FC<ChannelContextProps> = (props: ChannelContextPro
188189
onMessageAnimated,
189190
onMessageHighlighted,
190191
scrollBehavior = 'auto',
192+
reconnectOnIdle = true,
191193
} = props;
192194

193195
const globalStore = useSendbirdStateContext();
@@ -368,7 +370,7 @@ const ChannelProvider: React.FC<ChannelContextProps> = (props: ChannelContextPro
368370
}, [channelUrl, sdkInit]);
369371

370372
// handling connection breaks
371-
useHandleReconnect({ isOnline, replyType, disableMarkAsRead }, {
373+
useHandleReconnect({ isOnline, replyType, disableMarkAsRead, reconnectOnIdle }, {
372374
logger,
373375
sdk,
374376
scrollRef,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { GroupChannel } from '@sendbird/chat/groupChannel';
2+
import { renderHook, act } from '@testing-library/react';
3+
import useReconnectOnIdle from '../useReconnectOnIdle';
4+
5+
describe('useReconnectOnIdle', () => {
6+
beforeAll(() => {
7+
// Mock dispatchEvent for the document object
8+
document.dispatchEvent = jest.fn();
9+
jest.spyOn(document, 'addEventListener');
10+
jest.spyOn(document, 'removeEventListener');
11+
});
12+
13+
afterEach(() => {
14+
jest.clearAllMocks();
15+
});
16+
17+
it('should update shouldReconnect on tab visibility change', () => {
18+
const hook = renderHook(() => useReconnectOnIdle(true, { url: 'url' } as GroupChannel, true));
19+
expect(hook.result.current.shouldReconnect).toBe(false);
20+
21+
act(() => {
22+
Object.defineProperty(document, 'hidden', { value: false, configurable: true });
23+
document.dispatchEvent(new Event('visibilitychange'));
24+
});
25+
expect(hook.result.current.shouldReconnect).toBe(false);
26+
});
27+
28+
it('should not update shouldReconnect on isOnline change', () => {
29+
const { result, rerender } = renderHook(({ isOnline }) => useReconnectOnIdle(isOnline, { url: 'url' } as GroupChannel, true), {
30+
initialProps: { isOnline: false },
31+
});
32+
expect(result.current.shouldReconnect).toBe(true);
33+
34+
rerender({ isOnline: true });
35+
expect(result.current.shouldReconnect).toBe(false);
36+
});
37+
38+
it('should not update shouldReconnect if reconnectOnIdle is false', async () => {
39+
const reconnectOnIdle = false;
40+
renderHook(({ isOnline }) => useReconnectOnIdle(isOnline, { url: 'url' } as GroupChannel, reconnectOnIdle), {
41+
initialProps: { isOnline: false },
42+
});
43+
44+
const spy = jest.spyOn(document, 'addEventListener');
45+
document.dispatchEvent(new Event('visibilitychange'));
46+
47+
const lastCall = spy.mock.calls[spy.mock.calls.length - 1];
48+
expect(lastCall).toEqual(['visibilitychange', expect.any(Function)]);
49+
spy.mockRestore();
50+
});
51+
});

src/modules/Channel/context/hooks/useHandleReconnect.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import { PREV_RESULT_SIZE, NEXT_RESULT_SIZE } from '../const';
77
import * as messageActionTypes from '../dux/actionTypes';
88
import { Logger } from '../../../../lib/SendbirdState';
99
import { MarkAsReadSchedulerType } from '../../../../lib/hooks/useMarkAsReadScheduler';
10+
import useReconnectOnIdle from './useReconnectOnIdle';
1011

1112
interface DynamicParams {
1213
isOnline: boolean;
1314
replyType?: string;
1415
disableMarkAsRead: boolean;
16+
reconnectOnIdle: boolean;
1517
}
1618

1719
interface StaticParams {
@@ -25,7 +27,7 @@ interface StaticParams {
2527
}
2628

2729
function useHandleReconnect(
28-
{ isOnline, replyType, disableMarkAsRead }: DynamicParams,
30+
{ isOnline, replyType, disableMarkAsRead, reconnectOnIdle }: DynamicParams,
2931
{
3032
logger,
3133
sdk,
@@ -36,11 +38,12 @@ function useHandleReconnect(
3638
userFilledMessageListQuery,
3739
}: StaticParams,
3840
): void {
41+
const { shouldReconnect } = useReconnectOnIdle(isOnline, currentGroupChannel, reconnectOnIdle);
42+
3943
useEffect(() => {
40-
const wasOffline = !isOnline;
4144
return () => {
42-
// state changed from offline to online
43-
if (wasOffline && currentGroupChannel?.url) {
45+
// state changed from offline to online AND tab is visible
46+
if (shouldReconnect) {
4447
logger.info('Refreshing conversation state');
4548
const isReactionEnabled = sdk?.appInfo?.useReaction || false;
4649

@@ -97,7 +100,7 @@ function useHandleReconnect(
97100
});
98101
}
99102
};
100-
}, [isOnline, replyType]);
103+
}, [shouldReconnect, replyType]);
101104
}
102105

103106
export default useHandleReconnect;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useEffect, useState } from 'react';
2+
import type { GroupChannel } from '@sendbird/chat/groupChannel';
3+
4+
function useReconnectOnIdle(isOnline: boolean, currentGroupChannel: GroupChannel, reconnectOnIdle = true)
5+
:{ shouldReconnect: boolean }
6+
{
7+
const [isTabHidden, setIsTabHidden] = useState<boolean>(false);
8+
const wasOffline = !isOnline;
9+
10+
useEffect(() => {
11+
const handleVisibilityChange = () => {
12+
if (reconnectOnIdle) {
13+
setIsTabHidden(document.hidden);
14+
}
15+
};
16+
document.addEventListener('visibilitychange', handleVisibilityChange);
17+
18+
return () => {
19+
document.removeEventListener('visibilitychange', handleVisibilityChange);
20+
};
21+
}, [reconnectOnIdle, document.hidden]);
22+
23+
const shouldReconnect = wasOffline && currentGroupChannel?.url != null && !isTabHidden;
24+
return { shouldReconnect };
25+
}
26+
27+
export default useReconnectOnIdle;

src/modules/Channel/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const Channel: React.FC<ChannelProps> = (props: ChannelProps) => {
3737
onMessageAnimated={props?.onMessageAnimated}
3838
onMessageHighlighted={props?.onMessageHighlighted}
3939
scrollBehavior={props.scrollBehavior}
40+
reconnectOnIdle={props.reconnectOnIdle}
4041
>
4142
<ChannelUI
4243
isLoading={props?.isLoading}

0 commit comments

Comments
 (0)