-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
eat(react): useNetwork 신규 훅 추가 (#194)
- Loading branch information
Showing
5 changed files
with
205 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@modern-kit/react': minor | ||
--- | ||
|
||
feat(react): useNetwork 신규 훅 추가 - @ssi02014 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { useNetwork } from '@modern-kit/react'; | ||
|
||
# useNetwork | ||
|
||
`useSyncExternalStore`을 사용해 브라우저 네트워크 연결 상태를 구독 후 online 상태 여부를 반환하는 커스텀 훅입니다. | ||
|
||
`onlineCallback`, `offlineCallback`을 통해 네트워크 상태가 변경 될 때 원하는 함수를 호출 할 수 있습니다. | ||
|
||
<br /> | ||
|
||
## Code | ||
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/react/src/hooks/useNetwork/index.ts) | ||
|
||
## Interface | ||
|
||
```ts title="typescript" | ||
interface UseNetworkProps { | ||
onlineCallback?: (event: Event) => void; | ||
offlineCallback?: (event: Event) => void; | ||
} | ||
|
||
const useNetwork: ({ onlineCallback, offlineCallback }?: UseNetworkProps) => { | ||
isOnline: boolean; | ||
}; | ||
``` | ||
|
||
## Usage | ||
|
||
```tsx title="typescript" | ||
import { useNetwork } from '@modern-kit/react'; | ||
|
||
const Example = () => { | ||
const { isOnline } = useNetwork({ | ||
onlineCallback: () => { | ||
alert('online'); | ||
}, | ||
offlineCallback: () => { | ||
alert('offline'); | ||
}, | ||
}); | ||
|
||
return ( | ||
<div> | ||
<p>개발자 도구의 네트워크 탭에서 디바이스 연결을 끊어보세요.</p> | ||
<p>{isOnline ? '🟢 online' : '❌ offline'}</p> | ||
</div> | ||
); | ||
}; | ||
``` | ||
|
||
## Example | ||
|
||
export const Example = () => { | ||
const { isOnline } = useNetwork({ | ||
onlineCallback: () => { | ||
alert('online'); | ||
}, | ||
offlineCallback: () => { | ||
alert('offline'); | ||
}, | ||
}); | ||
|
||
return ( | ||
<div> | ||
<p>개발자 도구의 네트워크 탭에서 디바이스 연결을 끊어보세요.</p> | ||
<p>{isOnline ? '🟢 Online' : '❌ Offline'}</p> | ||
</div> | ||
); | ||
}; | ||
|
||
<Example /> | ||
|
||
## Note | ||
- [useSyncExternalStore(en) - react.dev](https://react.dev/reference/react/useSyncExternalStore) | ||
- [useSyncExternalStore(ko) - react.dev](https://ko.react.dev/reference/react/useSyncExternalStore) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { usePreservedCallback } from '../usePreservedCallback'; | ||
import { noop } from '@modern-kit/utils'; | ||
import { useSyncExternalStore } from 'react'; | ||
|
||
interface UseNetworkProps { | ||
onlineCallback?: (event: Event) => void; | ||
offlineCallback?: (event: Event) => void; | ||
} | ||
|
||
const getSnapshot = () => { | ||
return navigator.onLine; | ||
}; | ||
|
||
const getServerSnapshot = () => { | ||
return true; | ||
}; | ||
|
||
const subscribe = ( | ||
onStoreChange: () => void, | ||
onlineCallback: (event: Event) => void, | ||
offlineCallback: (event: Event) => void | ||
) => { | ||
const handleOnlineCallback = (event: Event) => { | ||
onlineCallback(event); | ||
return onStoreChange(); | ||
}; | ||
|
||
const handleOfflineCallback = (event: Event) => { | ||
offlineCallback(event); | ||
return onStoreChange(); | ||
}; | ||
|
||
window.addEventListener('online', handleOnlineCallback); | ||
window.addEventListener('offline', handleOfflineCallback); | ||
|
||
return () => { | ||
window.removeEventListener('online', handleOnlineCallback); | ||
window.removeEventListener('offline', handleOfflineCallback); | ||
}; | ||
}; | ||
|
||
export const useNetwork = ({ | ||
onlineCallback = noop, | ||
offlineCallback = noop, | ||
}: UseNetworkProps = {}) => { | ||
const preservedSubscribe = usePreservedCallback((onStoreChange: () => void) => | ||
subscribe(onStoreChange, onlineCallback, offlineCallback) | ||
); | ||
|
||
const isOnline = useSyncExternalStore( | ||
preservedSubscribe, | ||
getSnapshot, | ||
getServerSnapshot | ||
); | ||
|
||
return { isOnline }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { renderHook } from '@testing-library/react'; | ||
import { MockInstance } from 'vitest'; | ||
import { useNetwork } from '.'; | ||
import { renderToString } from 'react-dom/server'; | ||
|
||
let navigatorOnLineMockSpy: MockInstance; | ||
|
||
beforeEach(() => { | ||
navigatorOnLineMockSpy = vi.spyOn(navigator, 'onLine', 'get'); | ||
}); | ||
|
||
describe('useNetwork', () => { | ||
const onlineCallbackMock = vi.fn(); | ||
const offlineCallbackMock = vi.fn(); | ||
|
||
it('should return true for isOnline when online', () => { | ||
navigatorOnLineMockSpy.mockReturnValue(true); | ||
|
||
const { result } = renderHook(() => useNetwork()); | ||
|
||
expect(result.current.isOnline).toBeTruthy(); | ||
}); | ||
|
||
it('should return false for isOnline when offline', () => { | ||
navigatorOnLineMockSpy.mockReturnValue(false); | ||
|
||
const { result } = renderHook(() => useNetwork()); | ||
|
||
expect(result.current.isOnline).toBeFalsy(); | ||
}); | ||
|
||
it('should execute the registered callback when the network status changes', () => { | ||
navigatorOnLineMockSpy.mockReturnValue(true); | ||
|
||
renderHook(() => | ||
useNetwork({ | ||
onlineCallback: onlineCallbackMock, | ||
offlineCallback: offlineCallbackMock, | ||
}) | ||
); | ||
|
||
expect(offlineCallbackMock).not.toBeCalled(); | ||
expect(onlineCallbackMock).not.toBeCalled(); | ||
|
||
window.dispatchEvent(new Event('offline')); | ||
|
||
expect(offlineCallbackMock).toBeCalled(); | ||
|
||
window.dispatchEvent(new Event('online')); | ||
|
||
expect(onlineCallbackMock).toBeCalled(); | ||
}); | ||
|
||
it('should return true for isOnline during server-side rendering', () => { | ||
const TestComponent = () => { | ||
const { isOnline } = useNetwork({ | ||
onlineCallback: onlineCallbackMock, | ||
offlineCallback: offlineCallbackMock, | ||
}); | ||
return <p>{isOnline ? 'online' : 'offline'}</p>; | ||
}; | ||
|
||
const html = renderToString(<TestComponent />); // server side rendering | ||
|
||
expect(html).toContain('online'); | ||
}); | ||
}); |