-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(react): useBlockMultipleClick -> useBlockMultipleAsyncCalls 네이밍 변…
…경 및 문서 개선 (#628)
- Loading branch information
Showing
7 changed files
with
172 additions
and
91 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 | ||
--- | ||
|
||
fix(react): useBlockMultipleClick -> useBlockMultipleAsyncCalls 네이밍 변경 - @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
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
71 changes: 71 additions & 0 deletions
71
packages/react/src/hooks/useBlockMultipleAsyncCalls/index.ts
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,71 @@ | ||
import { useCallback, useRef, useState } from 'react'; | ||
|
||
interface UseBlockMultipleAsyncCallsReturnType { | ||
isLoading: boolean; | ||
blockMultipleAsyncCalls: <T>( | ||
callback: () => Promise<T> | ||
) => Promise<T | undefined>; | ||
} | ||
/** | ||
* @description `useBlockMultipleAsyncCalls` 훅은 진행 중인 비동기 호출이 있을 때 중복 호출을 방지하기 위한 커스텀 훅입니다. | ||
* | ||
* `debounce`는 함수의 중복 호출을 방지하는 데 대부분의 경우에 효과적입니다. | ||
* 하지만, `debounce`는 비동기 작업의 완료를 보장하지 않기 때문에 다음과 같은 한계가 있습니다: | ||
* | ||
* 1. `debounce` 시간이 API 응답 시간보다 짧을 경우: 비동기 작업이 완료되지 않은 상태에서 `다시 호출`될 수 있습니다. | ||
* 2. `debounce` 시간이 API 응답 시간보다 길 경우: 비동기 작업이 완료되었지만 `버튼`과 같은 요소가 여전히 `비활성화`되어 있을 수 있습니다. | ||
* 3. `즉각적인 반응`을 원하는 경우: `debounce`는 호출을 지연시키기 때문에 사용자에게 `즉각적인 반응`을 보여주기에 제한적입니다. | ||
* | ||
* 대부분의 경우에 `debounce`만으로 충분하지만, 위와 같은 한계점을 대응하고자 한다면 `useBlockMultipleAsyncCalls`를 사용할 수 있습니다. | ||
* | ||
* @returns {UseBlockMultipleAsyncCallsReturnType} 다음을 포함하는 객체: | ||
* - `isLoading`: 현재 비동기 작업이 진행 중인지 나타내는 불리언 값 | ||
* - `blockMultipleAsyncCalls`: 비동기 작업을 래핑하여 중복 호출을 방지하는 함수 | ||
* | ||
* @example | ||
* ```tsx | ||
* function MyComponent() { | ||
* const { isLoading, blockMultipleAsyncCalls } = useBlockMultipleAsyncCalls(); | ||
* | ||
* const fetchApi = async () => { | ||
* const data = await fetchData(); | ||
* // 데이터 처리 | ||
* }; | ||
* | ||
* const handleClick = () => { | ||
* blockMultipleAsyncCalls(fetchApi); | ||
* }; | ||
* | ||
* return <button onClick={handleClick} disabled={isLoading}>데이터 불러오기</button> | ||
* } | ||
* ``` | ||
*/ | ||
export function useBlockMultipleAsyncCalls(): UseBlockMultipleAsyncCallsReturnType { | ||
const [isLoading, setIsLoading] = useState(false); | ||
const isCalled = useRef(false); | ||
|
||
const blockMultipleAsyncCalls = useCallback( | ||
async <T>(callback: () => Promise<T>) => { | ||
if (isCalled.current) { | ||
return; | ||
} | ||
|
||
isCalled.current = true; | ||
setIsLoading(true); | ||
|
||
try { | ||
const result = await callback(); | ||
return result; | ||
} finally { | ||
isCalled.current = false; | ||
setIsLoading(false); | ||
} | ||
}, | ||
[] | ||
); | ||
|
||
return { | ||
isLoading, | ||
blockMultipleAsyncCalls, | ||
}; | ||
} |
75 changes: 75 additions & 0 deletions
75
packages/react/src/hooks/useBlockMultipleAsyncCalls/useBlockMultipleAsyncCalls.spec.tsx
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 { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; | ||
import { screen, renderHook, waitFor } from '@testing-library/react'; | ||
import { renderSetup } from '../../_internal/test/renderSetup'; | ||
import { useBlockMultipleAsyncCalls } from '.'; | ||
import { delay } from '@modern-kit/utils'; | ||
|
||
beforeEach(() => { | ||
vi.useFakeTimers({ shouldAdvanceTime: true }); | ||
}); | ||
|
||
afterEach(() => { | ||
vi.useRealTimers(); | ||
}); | ||
|
||
const DELAY_TIME = 1000; | ||
|
||
describe('useBlockMultipleAsyncCalls', () => { | ||
it('비동기 작업 완료 전 중복 호출 시 한 번만 실행되어야 합니다', async () => { | ||
const mockFn = vi.fn(async () => await delay(DELAY_TIME)); | ||
const { result } = renderHook(useBlockMultipleAsyncCalls); | ||
|
||
const { blockMultipleAsyncCalls } = result.current; | ||
expect(result.current.isLoading).toBe(false); | ||
|
||
blockMultipleAsyncCalls(mockFn); | ||
blockMultipleAsyncCalls(mockFn); | ||
blockMultipleAsyncCalls(mockFn); | ||
|
||
await waitFor(async () => { | ||
expect(result.current.isLoading).toBe(true); | ||
expect(mockFn).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
vi.advanceTimersByTime(DELAY_TIME); | ||
|
||
await waitFor(async () => { | ||
expect(result.current.isLoading).toBe(false); | ||
}); | ||
|
||
vi.advanceTimersByTime(DELAY_TIME); | ||
|
||
await waitFor(async () => { | ||
expect(mockFn).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
|
||
// 이해를 돕기 위해 컴포넌트 예제 추가 | ||
it('버튼을 여러 번 클릭해도 비동기 작업이 완료되기 전까지는 한 번만 실행되어야 합니다.', async () => { | ||
const mockFn = vi.fn(async () => await delay(DELAY_TIME)); | ||
const { result } = renderHook(useBlockMultipleAsyncCalls); | ||
|
||
const { blockMultipleAsyncCalls } = result.current; | ||
const onClick = () => blockMultipleAsyncCalls(mockFn); | ||
|
||
const { user } = renderSetup(<button onClick={onClick}>TestButton</button>); | ||
const button = screen.getByRole('button'); | ||
|
||
expect(result.current.isLoading).toBe(false); | ||
|
||
await user.click(button); | ||
await user.click(button); | ||
await user.click(button); | ||
|
||
await waitFor(async () => { | ||
expect(result.current.isLoading).toBe(true); | ||
expect(mockFn).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
vi.advanceTimersByTime(DELAY_TIME); | ||
|
||
await waitFor(async () => { | ||
expect(result.current.isLoading).toBe(false); | ||
}); | ||
}); | ||
}); |
31 changes: 0 additions & 31 deletions
31
packages/react/src/hooks/useBlockPromiseMultipleClick/index.ts
This file was deleted.
Oops, something went wrong.
46 changes: 0 additions & 46 deletions
46
packages/react/src/hooks/useBlockPromiseMultipleClick/useBlockPromiseMultipleClick.spec.tsx
This file was deleted.
Oops, something went wrong.