Skip to content

Commit 64d39e7

Browse files
authored
Merge pull request #47 from frontend-opensource-project/URH-58/use-online-status
[URH-58] useOnlineStatus 신규
2 parents 0a649ca + 9f373a2 commit 64d39e7

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

src/hooks/useOnlineStatus.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { act, renderHook } from '@testing-library/react';
2+
import useOnlineStatus from './useOnlineStatus';
3+
4+
let windowSpy: jest.SpyInstance;
5+
let onlineCallback: jest.Mock;
6+
let offlineCallback: jest.Mock;
7+
8+
describe('useOnlineStatus', () => {
9+
beforeEach(() => {
10+
windowSpy = jest.spyOn(global, 'window', 'get');
11+
onlineCallback = jest.fn();
12+
offlineCallback = jest.fn();
13+
});
14+
15+
afterEach(() => {
16+
windowSpy.mockRestore();
17+
});
18+
19+
test('온라인 상태라면 isOnline은 true이다.', () => {
20+
jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(true);
21+
22+
const { result } = renderHook(() => useOnlineStatus());
23+
24+
expect(result.current.isOnline).toBe(true);
25+
});
26+
27+
test('오프라인 상태에서 isOnline은 false이다.', () => {
28+
jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(false);
29+
30+
const { result } = renderHook(() => useOnlineStatus());
31+
32+
expect(result.current.isOnline).toBe(false);
33+
});
34+
35+
test('네트워크 상태가 변경되면 isOnline 상태가 갱신된다.', () => {
36+
const { result } = renderHook(() => useOnlineStatus());
37+
38+
act(() => {
39+
window.dispatchEvent(new Event('offline'));
40+
});
41+
42+
expect(result.current.isOnline).toBe(false);
43+
44+
act(() => {
45+
window.dispatchEvent(new Event('online'));
46+
});
47+
48+
expect(result.current.isOnline).toBe(true);
49+
});
50+
51+
test('콜백 함수가 호출되는지 확인', () => {
52+
renderHook(() => useOnlineStatus({ onlineCallback, offlineCallback }));
53+
act(() => {
54+
window.dispatchEvent(new Event('offline'));
55+
});
56+
57+
expect(offlineCallback).toHaveBeenCalledTimes(1);
58+
59+
act(() => {
60+
window.dispatchEvent(new Event('online'));
61+
});
62+
expect(onlineCallback).toHaveBeenCalledTimes(1);
63+
});
64+
65+
test('언마운트 시 이벤트 리스너가 제거된다.', () => {
66+
const { unmount } = renderHook(() =>
67+
useOnlineStatus({ onlineCallback, offlineCallback })
68+
);
69+
70+
unmount();
71+
72+
act(() => {
73+
window.dispatchEvent(new Event('offline'));
74+
});
75+
expect(offlineCallback).toHaveBeenCalledTimes(0);
76+
77+
act(() => {
78+
window.dispatchEvent(new Event('online'));
79+
});
80+
expect(onlineCallback).toHaveBeenCalledTimes(0);
81+
});
82+
});

src/hooks/useOnlineStatus.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useEffect, useState } from 'react';
2+
import { Fn } from '../types';
3+
import { hasNavigator } from '../utils';
4+
5+
interface useOnlineStatusProps {
6+
onlineCallback?: Fn;
7+
offlineCallback?: Fn;
8+
}
9+
10+
interface UseOnlineStatusReturns {
11+
isOnline: boolean;
12+
}
13+
14+
/**
15+
* 온라인/오프라인 네트워크 상태를 판별하는 훅.
16+
*
17+
* @param {Object} props - 훅에 전달되는 옵션 객체
18+
* @param {Fn} [props.onlineCallback] - 브라우저가 온라인 상태가 될 때 실행할 콜백 함수
19+
* @param {Fn} [props.offlineCallback] - 브라우저가 오프라인 상태가 될 때 실행할 콜백 함수
20+
*
21+
* @returns {{ isOnline: boolean }} - 현재 온라인 상태를 나타내는 객체
22+
*
23+
* @description
24+
* 브라우저의 온라인/오프라인 상태를 추적하는 훅입니다.
25+
* 온라인 상태가 변경될 때 실행할 콜백 함수를 선택적으로 지정할 수 있습니다.
26+
* 콜백 함수들은 `useCallback`을 사용하여 메모이제이션 할 것을 권장합니다.
27+
* 이를 통해 의도하지 않은 재생성을 방지하고 성능을 최적화할 수 있습니다.
28+
*/
29+
30+
const useOnlineStatus = ({
31+
onlineCallback = () => {},
32+
offlineCallback = () => {},
33+
}: useOnlineStatusProps = {}): UseOnlineStatusReturns => {
34+
const [isOnline, setIsOnline] = useState(() =>
35+
hasNavigator() ? navigator.onLine : false
36+
);
37+
38+
useEffect(() => {
39+
if (!hasNavigator()) {
40+
console.error('navigator is not supported in this environment.');
41+
return;
42+
}
43+
44+
const handleOnline = () => {
45+
setIsOnline(true);
46+
onlineCallback();
47+
};
48+
49+
const handleOffline = () => {
50+
setIsOnline(false);
51+
offlineCallback();
52+
};
53+
54+
window.addEventListener('online', handleOnline);
55+
window.addEventListener('offline', handleOffline);
56+
57+
return () => {
58+
window.removeEventListener('online', handleOnline);
59+
window.removeEventListener('offline', handleOffline);
60+
};
61+
}, [onlineCallback, offlineCallback]);
62+
63+
return {
64+
isOnline,
65+
};
66+
};
67+
68+
export default useOnlineStatus;

src/utils/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export { delayExecution, type CancelToken } from './delayExecution';
22
export { throttle } from './throttle';
3-
export { isClient } from './isClient';
3+
export { isClient, hasNavigator } from './isClient';
44
export { isTouchDevice } from './isTouchDevice';
55
export { validators, MatchError } from './validators';
66
export { imgToBlob } from './imgToBlob';

src/utils/isClient.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
export const isClient = typeof window !== 'undefined';
2+
3+
export const hasNavigator = () => {
4+
return isClient && typeof navigator !== 'undefined';
5+
};

0 commit comments

Comments
 (0)