-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add cancel and reset methods to useTimeout
BREAKING CHANGE: useTimeout now returns a tuple
- Loading branch information
Showing
5 changed files
with
201 additions
and
24 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
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 |
---|---|---|
@@ -1,15 +1,48 @@ | ||
# `useTimeout` | ||
|
||
Returns `true` after a specified number of milliseconds. | ||
Re-renders the component after a specified number of milliseconds. | ||
Provides handles to cancel and/or reset the timeout. | ||
|
||
## Usage | ||
|
||
```jsx | ||
import { useTimeout } from 'react-use'; | ||
|
||
const Demo = () => { | ||
const ready = useTimeout(2000); | ||
function TestComponent(props: { ms?: number } = {}) { | ||
const ms = props.ms || 5000; | ||
const [isReady, cancel] = useTimeout(ms); | ||
|
||
return ( | ||
<div> | ||
{ isReady() ? 'I\'m reloaded after timeout' : `I will be reloaded after ${ ms / 1000 }s` } | ||
{ isReady() === false ? <button onClick={ cancel }>Cancel</button> : '' } | ||
</div> | ||
); | ||
} | ||
|
||
return <div>Ready: {ready ? 'Yes' : 'No'}</div>; | ||
const Demo = () => { | ||
return ( | ||
<div> | ||
<TestComponent /> | ||
<TestComponent ms={ 10000 } /> | ||
</div> | ||
); | ||
}; | ||
``` | ||
|
||
## Reference | ||
|
||
```ts | ||
const [ | ||
isReady: () => boolean | null, | ||
cancel: () => void, | ||
reset: () => void, | ||
] = useTimeout(ms: number = 0); | ||
``` | ||
|
||
- **`isReady`**_` :()=>boolean|null`_ - function returning current timeout state: | ||
- `false` - pending re-render | ||
- `true` - re-render performed | ||
- `null` - re-render cancelled | ||
- **`cancel`**_` :()=>void`_ - cancel the timeout (component will not be re-rendered) | ||
- **`reset`**_` :()=>void`_ - reset the timeout |
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,138 @@ | ||
import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks'; | ||
import { useTimeout } from '../index'; | ||
import { UseTimeoutReturn } from '../useTimeout'; | ||
|
||
describe('useTimeout', () => { | ||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(useTimeout).toBeDefined(); | ||
}); | ||
|
||
it('should return three functions', () => { | ||
const hook = renderHook(() => useTimeout(5)); | ||
|
||
expect(hook.result.current.length).toBe(3); | ||
expect(typeof hook.result.current[0]).toBe('function'); | ||
expect(typeof hook.result.current[1]).toBe('function'); | ||
expect(typeof hook.result.current[2]).toBe('function'); | ||
}); | ||
|
||
function getHook(ms: number = 5): [jest.Mock, RenderHookResult<{ delay: number }, UseTimeoutReturn>] { | ||
const spy = jest.fn(); | ||
return [ | ||
spy, | ||
renderHook( | ||
({ delay = 5 }) => { | ||
spy(); | ||
return useTimeout(delay); | ||
}, | ||
{ initialProps: { delay: ms } } | ||
), | ||
]; | ||
} | ||
|
||
it('should re-render component after given amount of time', done => { | ||
const [spy, hook] = getHook(); | ||
expect(spy).toHaveBeenCalledTimes(1); | ||
hook.waitForNextUpdate().then(() => { | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
done(); | ||
}); | ||
jest.advanceTimersByTime(5); | ||
}); | ||
|
||
it('should cancel timeout on unmount', () => { | ||
const [spy, hook] = getHook(); | ||
|
||
expect(spy).toHaveBeenCalledTimes(1); | ||
hook.unmount(); | ||
jest.advanceTimersByTime(5); | ||
expect(spy).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('first function should return actual state of timeout', done => { | ||
let [, hook] = getHook(); | ||
let [isReady] = hook.result.current; | ||
|
||
expect(isReady()).toBe(false); | ||
hook.unmount(); | ||
expect(isReady()).toBe(null); | ||
|
||
[, hook] = getHook(); | ||
[isReady] = hook.result.current; | ||
hook.waitForNextUpdate().then(() => { | ||
expect(isReady()).toBe(true); | ||
|
||
done(); | ||
}); | ||
jest.advanceTimersByTime(5); | ||
}); | ||
|
||
it('second function should cancel timeout', () => { | ||
const [spy, hook] = getHook(); | ||
const [isReady, cancel] = hook.result.current; | ||
|
||
expect(spy).toHaveBeenCalledTimes(1); | ||
expect(isReady()).toBe(false); | ||
|
||
act(() => { | ||
cancel(); | ||
}); | ||
jest.advanceTimersByTime(5); | ||
|
||
expect(spy).toHaveBeenCalledTimes(1); | ||
expect(isReady()).toBe(null); | ||
}); | ||
|
||
it('third function should reset timeout', done => { | ||
const [spy, hook] = getHook(); | ||
const [isReady, cancel, reset] = hook.result.current; | ||
|
||
expect(isReady()).toBe(false); | ||
|
||
act(() => { | ||
cancel(); | ||
}); | ||
jest.advanceTimersByTime(5); | ||
|
||
expect(isReady()).toBe(null); | ||
|
||
act(() => { | ||
reset(); | ||
}); | ||
expect(isReady()).toBe(false); | ||
|
||
hook.waitForNextUpdate().then(() => { | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
expect(isReady()).toBe(true); | ||
|
||
done(); | ||
}); | ||
jest.advanceTimersByTime(5); | ||
}); | ||
|
||
it('should reset timeout on delay change', done => { | ||
const [spy, hook] = getHook(15); | ||
|
||
expect(spy).toHaveBeenCalledTimes(1); | ||
hook.rerender({ delay: 5 }); | ||
|
||
hook.waitForNextUpdate().then(() => { | ||
expect(spy).toHaveBeenCalledTimes(3); | ||
|
||
done(); | ||
}); | ||
jest.advanceTimersByTime(15); | ||
}); | ||
}); |
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 |
---|---|---|
@@ -1,19 +1,10 @@ | ||
import { useEffect, useState } from 'react'; | ||
import useTimeoutFn from './useTimeoutFn'; | ||
import useUpdate from './useUpdate'; | ||
|
||
const useTimeout = (ms: number = 0) => { | ||
const [ready, setReady] = useState(false); | ||
export type UseTimeoutReturn = [() => boolean | null, () => void, () => void]; | ||
|
||
useEffect(() => { | ||
const timer = setTimeout(() => { | ||
setReady(true); | ||
}, ms); | ||
export default function useTimeout(ms: number = 0): UseTimeoutReturn { | ||
const update = useUpdate(); | ||
|
||
return () => { | ||
clearTimeout(timer); | ||
}; | ||
}, [ms]); | ||
|
||
return ready; | ||
}; | ||
|
||
export default useTimeout; | ||
return useTimeoutFn(update, ms); | ||
} |