diff --git a/.changeset/fluffy-waves-design.md b/.changeset/fluffy-waves-design.md new file mode 100644 index 0000000..fa6dfe0 --- /dev/null +++ b/.changeset/fluffy-waves-design.md @@ -0,0 +1,5 @@ +--- +'@raddix/use-timeout': minor +--- + +Added two new return functions: `reset` and `run` diff --git a/packages/hooks/use-timeout/README.md b/packages/hooks/use-timeout/README.md index d85c07e..497bc47 100644 --- a/packages/hooks/use-timeout/README.md +++ b/packages/hooks/use-timeout/README.md @@ -2,4 +2,4 @@ The `useTimeout` hook is used to execute a callback after a specified amount of time. It is similar to the `setTimeout` function, but it is declarative and can be cancelled. -Please refer to the [documentation](https://www.raddix.website/docs/interactions/use-timeout) for more information. +Please refer to the [documentation](https://raddix.website/hooks/use-timeout) for more information. diff --git a/packages/hooks/use-timeout/src/index.ts b/packages/hooks/use-timeout/src/index.ts index 973320e..93c1dcf 100644 --- a/packages/hooks/use-timeout/src/index.ts +++ b/packages/hooks/use-timeout/src/index.ts @@ -1,26 +1,45 @@ -import { useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; -type UseTimeout = (cb: () => void, delay: number) => () => void; - -export const useTimeout: UseTimeout = (cb, delay) => { +/** + * A React hook for handling timeouts + * @param cb The callback function to be executed after the specified timeout. + * @param delay The duration of the timeout in milliseconds. + * @param immediate If it is true, the callback is executed immediately. + * @see https://raddix.website/hooks/use-timeout + */ +export const useTimeout = ( + cb: () => void, + delay: number | null, + immediate = true +): { clear: () => void; reset: () => void; run: () => void } => { const savedCallback = useRef(cb); const id = useRef(null); - const clearId = () => { + const clear = useCallback(() => { if (!id.current) return; clearTimeout(id.current); id.current = null; - }; + }, []); + + const run = useCallback(() => { + if (id.current) return; + if (delay === null) return; + id.current = setTimeout(() => savedCallback.current(), delay); + }, [delay]); + + const reset = useCallback(() => { + clear(); + run(); + }, [run, clear]); useEffect(() => { savedCallback.current = cb; }, [cb]); useEffect(() => { - const tick = () => savedCallback.current(); - id.current = setTimeout(tick, delay); - return () => clearId(); - }, [delay]); + if (immediate) run(); + return () => clear(); + }, [delay, run, clear, immediate]); - return clearId; + return { clear, reset, run }; }; diff --git a/packages/hooks/use-timeout/tests/use-timeout.test.ts b/packages/hooks/use-timeout/tests/use-timeout.test.ts index 5cc89b1..f22d65e 100644 --- a/packages/hooks/use-timeout/tests/use-timeout.test.ts +++ b/packages/hooks/use-timeout/tests/use-timeout.test.ts @@ -46,14 +46,57 @@ describe('useTimeout test:', () => { expect(callback).toHaveBeenCalledTimes(1); }); + it('should not execute the callback if the delay is null', () => { + const callback = jest.fn(); + renderHook(() => useTimeout(callback, null)); + + jest.advanceTimersByTime(2000); + expect(callback).not.toBeCalled(); + }); + + it('should not execute the callback immediately if the inmediate option is false', () => { + const callback = jest.fn(); + renderHook(() => useTimeout(callback, 1000, false)); + + jest.advanceTimersByTime(2000); + expect(callback).not.toBeCalled(); + }); + it('should stop the timeout when the clear function is called', () => { const callback = jest.fn(); const { result } = renderHook(() => useTimeout(callback, 4000)); expect(callback).not.toBeCalled(); jest.advanceTimersByTime(2000); - result.current(); + result.current.clear(); jest.advanceTimersByTime(2000); expect(callback).not.toBeCalled(); }); + + it('should restart the timeout when the reset function is called', () => { + const callback = jest.fn(); + const { result } = renderHook(() => useTimeout(callback, 4000)); + + expect(callback).not.toBeCalled(); + jest.advanceTimersByTime(2000); + result.current.reset(); + jest.advanceTimersByTime(2000); + expect(callback).not.toBeCalled(); + jest.advanceTimersByTime(2000); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should execute the callback if the run function is called', () => { + const callback = jest.fn(); + const { result } = renderHook(() => useTimeout(callback, 2000, false)); + + jest.advanceTimersByTime(2000); + expect(callback).not.toBeCalled(); + result.current.run(); + jest.advanceTimersByTime(4000); + expect(callback).toHaveBeenCalledTimes(1); + result.current.run(); + jest.advanceTimersByTime(2000); + expect(callback).toHaveBeenCalledTimes(1); + }); });