Skip to content

Commit

Permalink
fix: useDependencyTimeout의 인터페이스 변경
Browse files Browse the repository at this point in the history
  • Loading branch information
Collection50 committed Dec 4, 2024
1 parent 83076fc commit 4009683
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 29 deletions.
78 changes: 78 additions & 0 deletions docs/docs/react/hooks/useDependencyTimeout.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useDependencyTimeout } from '@modern-kit/react';
import { useState, DependencyList } from 'react';

# useDependencyTimeout

`useTimeout`를 dependency로 관리해 사용할 수 있는 커스텀 훅 입니다.

`callback` 함수, `delay` 숫자, `deps` 배열, 첫 마운트 여부를 수행하는 `{ callOnMount }`를 받습니다.

Timeout을 직접 핸들링 할 수 있는 `set`, `reset`, `clear` 함수를 반환합니다.

<br />

## Code
[🔗 실제 구현 코드 확인](https://github.com/modern-agile-team/modern-kit/blob/main/packages/react/src/hooks/useDependencyTimeout/index.ts)

## Interface
```ts title="typescript"
interface TimeoutOptions {
callback: () => void;
options: number | TimeoutOptions;
deps: DependencyList[];
{ callOnMount }: { callOnMount: boolean }
}
```

## Usage
```tsx title="typescript"
import { useDependencyTimeout } from '@modern-kit/react';

const Example = () => {
const [number, setNumber] = useState(0);

const { set, reset, clear } = useDependencyTimeout(() => {
setNumber(number + 1);
}, 1000);

/*
* useDependencyTimeout(() => {
* setNumber(number + 1);
* }, { delay: 1000, enabled: true });
*/

return (
<div>
<p>{number}</p>
<div>
<button onClick={() => set()}>set</button>
<button onClick={() => reset()}>reset</button>
<button onClick={() => clear()}>clear</button>
</div>
</div>
);
};
```

## Example

export const Example = ({ deps }) => {
const [number, setNumber] = useState(0);

const { set, reset, clear } = useDependencyTimeout(() => {
setNumber(number + 1);
}, { delay: 1000, enabled: true }, deps);

return (
<div>
<p>{number}</p>
<div>
<button onClick={() => set()}>set</button>
<button onClick={() => reset()}>reset</button>
<button onClick={() => clear()}>clear</button>
</div>
</div>
);
};

<Example />
1 change: 1 addition & 0 deletions packages/react/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ export * from './useUserAgent';
export * from './useVhProperty';
export * from './useVisibilityChange';
export * from './useWindowSize';
export * from './useDependencyTimeout';
47 changes: 28 additions & 19 deletions packages/react/src/hooks/useDependencyTimeout/index.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,46 @@
import { useEffect, useRef } from 'react';
import { useEffect, DependencyList } from 'react';
import { useTimeout } from 'hooks/useTimeout';
import { getTimeoutOptions } from 'hooks/useTimeout/useTimeout.utils';
import type { TimeoutOptions } from 'hooks/useTimeout/useTimeout.types';

export function useDependencyTimeout(
callback: () => void,
delay: number,
deps: DependencyList
): ReturnType<typeof useTimeout>;

export function useDependencyTimeout(
callback: () => void,
options: TimeoutOptions,
deps: DependencyList
): ReturnType<typeof useTimeout>;

/**
* @description useTimeout를 dependency 배열로 제어하는 훅입니다.
*
* @param callback: 실행 할 콜백
* @param delay: timeout delay
* @param deps: 의존성 배열
* @param callOnMount: 최초 마운트 시에 callback 호출 할 것인지 결정 *
* @param {() => void} callback - delay 후에 실행될 함수입니다.
* @param {number | TimeoutOptions} options - 밀리초(ms) 단위의 지연 시간 또는 옵션 객체(delay, enabled)입니다.
* @param {DependencyList} deps - 의존성 배열
*
* @return {ReturnType<typeof useTimeout>}
* @example
* useDependencyTimeout(callback, 300, [condition]);
*
* @example
* const { set, reset, clear } = useDependencyTimeout(() => console.log(something), 500, deps);
* useDependencyTimeout(callback, { delay: 300, enabled }, [condition]);
*/
export default function useDependencyTimeout(
callback: () => void,
delay: number,
deps: unknown[],
{ callOnMount }: { callOnMount: boolean }
) {
const isFirstMount = useRef(true);
options: number | TimeoutOptions,
deps: DependencyList
): ReturnType<typeof useTimeout> {
const { delay, enabled } = getTimeoutOptions(options);

const { set, reset, clear } = useTimeout(callback, delay);

useEffect(() => {
if (isFirstMount.current) {
if (callOnMount) {
callback();
}
isFirstMount.current = false;
}
if (delay < 0 || !enabled) return;
reset();
}, [callOnMount, callback, ...deps]);
}, [delay, enabled, reset, ...deps]);

Check warning on line 43 in packages/react/src/hooks/useDependencyTimeout/index.ts

View workflow job for this annotation

GitHub Actions / Build-Test

React Hook useEffect has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies

return { set, reset, clear };
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ afterEach(() => {
});

describe('useDependencyTimeout', () => {
it('callOnMount가 true면 마운트 시 콜백을 호출해야 합니다.', () => {
it('마운트 시 callOnMount가 true면 콜백을 호출해야 합니다.', () => {
const mockFn = vi.fn();

renderHook(() =>
useDependencyTimeout(mockFn, delayTime, [], { callOnMount: true })
useDependencyTimeout(mockFn, { delay: delayTime, enabled: true }, [])
);

expect(mockFn).toBeCalledTimes(1);
Expand All @@ -27,11 +27,11 @@ describe('useDependencyTimeout', () => {
expect(mockFn).toBeCalledTimes(2);
});

it('callOnMount가 false면 마운트 시 콜백을 호출하지 않아야 합니다.', () => {
it('마운트 시 callOnMount가 false면 콜백을 호출하지 않아야 합니다.', () => {
const mockFn = vi.fn();

renderHook(() =>
useDependencyTimeout(mockFn, delayTime, [], { callOnMount: false })
useDependencyTimeout(mockFn, { delay: delayTime, enabled: true }, [])
);

expect(mockFn).not.toBeCalled();
Expand All @@ -41,12 +41,12 @@ describe('useDependencyTimeout', () => {
expect(mockFn).toBeCalledTimes(1);
});

it('의존성이 변경될 때 타임아웃을 리셋해야 합니다.', () => {
it('의존성이 변경되면 타임아웃이 리셋되어야 합니다.', () => {
const mockFn = vi.fn();

const { rerender } = renderHook(
({ deps }) =>
useDependencyTimeout(mockFn, delayTime, deps, { callOnMount: false }),
useDependencyTimeout(mockFn, { delay: delayTime, enabled: true }, deps),
{
initialProps: { deps: [1] },
}
Expand All @@ -65,11 +65,11 @@ describe('useDependencyTimeout', () => {
expect(mockFn).toBeCalledTimes(1);
});

it('타임아웃을 해제할 수 있어야 합니다.', () => {
it('타임아웃을 수동으로 해제할 수 있어야 합니다.', () => {
const mockFn = vi.fn();

const { result } = renderHook(() =>
useDependencyTimeout(mockFn, delayTime, [], { callOnMount: false })
useDependencyTimeout(mockFn, { delay: delayTime, enabled: true }, [])
);

vi.advanceTimersByTime(delayTime / 2);
Expand All @@ -86,12 +86,34 @@ describe('useDependencyTimeout', () => {
it('타임아웃을 수동으로 리셋할 수 있어야 합니다.', () => {
const mockFn = vi.fn();

const { result } = renderHook(() =>
useDependencyTimeout(mockFn, { delay: delayTime, enabled: true }, [])
);

vi.advanceTimersByTime(delayTime / 2);

act(() => {
result.current.reset();
});

vi.advanceTimersByTime(delayTime / 2);

expect(mockFn).not.toBeCalled();

vi.advanceTimersByTime(delayTime / 2);

expect(mockFn).toBeCalledTimes(1);
});

it('enabled가 false면 타임아웃이 동작하지 않아야 합니다.', () => {
const mockFn = vi.fn();

renderHook(() =>
useDependencyTimeout(mockFn, delayTime, [], { callOnMount: false })
useDependencyTimeout(mockFn, { delay: delayTime, enabled: false }, [])
);

vi.advanceTimersByTime(delayTime);

expect(mockFn).toBeCalledTimes(1);
expect(mockFn).not.toBeCalled();
});
});

0 comments on commit 4009683

Please sign in to comment.