-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(useCountDown): new improved API (#53)
Added a new API that provides more flexibility and also improved the countdown logic
- Loading branch information
Showing
4 changed files
with
189 additions
and
125 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,7 @@ | ||
--- | ||
'@raddix/use-count-down': major | ||
--- | ||
|
||
The hook API has been changed because it now provides more flexibility and is easier to use. | ||
|
||
The isFinished value was temporarily removed, it will be re-incorporated in a future feature of this release. |
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,90 @@ | ||
<div align="center"> | ||
<h1 align="center">useCountDown</h1> | ||
<a href="https://github.com/gdvu/raddix/blob/main/LICENSE"> | ||
<img alt="GitHub" src="https://img.shields.io/github/license/gdvu/raddix"> | ||
</a> | ||
<a href="https://www.npmjs.com/package/@raddix/use-count-down"> | ||
<img alt="npm version" src="https://img.shields.io/npm/v/@raddix/use-count-down"> | ||
</a> | ||
|
||
<a href="https://www.npmjs.com/package/@raddix/use-count-down"> | ||
<img alt="npm bundle size" src="https://img.shields.io/bundlephobia/min/@raddix/use-count-down"> | ||
</a> | ||
</div> | ||
<span></span> | ||
<p align="center"> | ||
The useCountdown hook is useful for creating a very simple yet powerful countdown timer for React. | ||
</p> | ||
|
||
## Install | ||
|
||
```bash | ||
npm i @raddix/use-count-down | ||
# or | ||
yarn add @raddix/use-count-down | ||
# or | ||
pnpm add @raddix/use-count-down | ||
``` | ||
|
||
## Quick Start | ||
|
||
```jsx | ||
import { useCountDown } from '@raddix/use-count-down'; | ||
|
||
// Using it in a basic way | ||
const Component = () => { | ||
const [count, { reset }] = useCountDown(10000); | ||
|
||
return ( | ||
<div> | ||
<span>{Math.round(count / 1000)}</span> | ||
<button onClick={() => reset()}>Reset</button> | ||
</div> | ||
); | ||
}; | ||
|
||
// Using it in an advanced way, changing its settings | ||
const Component = () => { | ||
const [count, { start, stop, reset }] = useCountDown(12000, { | ||
interval: 500, | ||
autoStart: false | ||
}); | ||
|
||
return ( | ||
<div> | ||
<span>{Math.round(count / 1000)}</span> | ||
<button onClick={() => start()}>Start</button> | ||
<button onClick={() => stop()}>Stop</button> | ||
<button onClick={() => reset()}>Reset</button> | ||
</div> | ||
); | ||
}; | ||
``` | ||
|
||
## API | ||
|
||
### Parameters | ||
|
||
| Argument | Type | Required | Description | | ||
| ----------- | --------- | -------- | ------------------------------------------------------- | | ||
| initialTime | `number` | Yes | Time in milliseconds that countdown should start with | | ||
| options | `Options` | No | A configuration object with the following options below | | ||
|
||
`Options` | ||
| Argument | Type | Required | Description | | ||
| -------- | -------- | --------- | ------------------------------------------------------------------------------------ | | ||
| interval | `number` | No | The time, in milliseconds, that the timer should count down. | | ||
| autoStart | `boolean` | No | Start timer immediately | | ||
| onFinished | `() => void` | No | A callback function to be called when the countdown reaches zero. | | ||
| onTick | `() => void` | No | A callback function to be called on each specified interval of the countdown. | | ||
|
||
### Returns | ||
|
||
The `useCountDown` hook returns an array with two elements: | ||
|
||
| Index | Type | Parameters | Description | | ||
| ----------- | ---------- | ----------------- | -------------------------------------------------------------------------------------------------------- | | ||
| `[0]` | `number` | | The current count of the countdown. | | ||
| `[1].start` | `function` | `(time?: number)` | Start and resume the countdown, also restart from the time (in milliseconds) indicated in the parameter. | | ||
| `[1].reset` | `function` | | Resets the countdown to its initial setting. | | ||
| `[1].stop` | `function` | | Pause the countdown. | |
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,83 +1,95 @@ | ||
import { useState, useEffect, useRef, useCallback } from 'react'; | ||
|
||
interface Options { | ||
/** | ||
* The time, in milliseconds, that the timer should count down. | ||
* @default 1000 | ||
*/ | ||
interval?: number; | ||
/** | ||
* Start timer immediately | ||
* @default true | ||
*/ | ||
autoStart?: boolean; | ||
/** A callback function to be called when the countdown reaches zero. */ | ||
onFinished?: () => void; | ||
/** A callback function to be called on each specified interval of the countdown. */ | ||
onTick?: () => void; | ||
} | ||
|
||
interface CountDownResult { | ||
readonly value: number; | ||
readonly stop: () => void; | ||
readonly trigger: () => void; | ||
readonly reset: () => void; | ||
readonly isFinished: boolean; | ||
interface Actions { | ||
/** Start and resume the countdown, also restart from the time (in milliseconds) indicated in the parameter */ | ||
start: (time?: number) => void; | ||
/** Resets the countdown to its initial setting */ | ||
reset: () => void; | ||
/** Pause the countdown */ | ||
stop: () => void; | ||
} | ||
|
||
type UseCountDown = ( | ||
initialValue: number, | ||
interval: number, | ||
initialTime: number, | ||
options?: Options | ||
) => CountDownResult; | ||
) => [count: number, actions: Actions]; | ||
|
||
export const useCountDown: UseCountDown = ( | ||
initialValue, | ||
interval = 1000, | ||
options = {} | ||
) => { | ||
const { autoStart = true, onFinished, onTick } = options; | ||
// plus and minus the current time | ||
const plus = (x: number) => x + Date.now(); | ||
const minus = (x: number) => x - Date.now(); | ||
|
||
const [timer, setTimer] = useState(initialValue); | ||
const [isFinished, setIsFinished] = useState<boolean>(false); | ||
export const useCountDown: UseCountDown = (initialTime, options = {}) => { | ||
const { onFinished, onTick, interval = 1000, autoStart = true } = options; | ||
|
||
const [startUp, setstartUp] = useState<boolean>(autoStart); | ||
const [initialCount, setInitialCount] = useState<number>(plus(initialTime)); | ||
const [count, setCount] = useState<number>(initialTime); | ||
const timerRef = useRef<NodeJS.Timer | null>(null); | ||
|
||
const stop = useCallback(() => { | ||
const clearTimer = () => { | ||
if (!timerRef.current) return; | ||
|
||
clearInterval(timerRef.current); | ||
timerRef.current = null; | ||
}, []); | ||
|
||
const trigger = useCallback(() => { | ||
if (timerRef.current) return; | ||
const timerDate = Date.now() + timer; | ||
}; | ||
|
||
const timer = useCallback(() => { | ||
timerRef.current = setInterval(() => { | ||
let distance = timerDate - Date.now(); | ||
distance = distance < 0 ? 0 : distance; | ||
let leftTime = initialCount - Date.now(); | ||
leftTime = leftTime <= 0 ? 0 : leftTime; | ||
setCount(leftTime); | ||
onTick?.(); | ||
|
||
setTimer(distance); | ||
if (onTick) onTick(); | ||
|
||
if (distance <= 0) { | ||
stop(); | ||
setIsFinished(true); | ||
if (onFinished) onFinished(); | ||
if (leftTime === 0) { | ||
clearTimer(); | ||
onFinished?.(); | ||
} | ||
}, interval); | ||
//eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [timer, interval]); | ||
}, [initialCount, interval]); | ||
|
||
const reset = useCallback(() => { | ||
setTimer(initialValue); | ||
setIsFinished(true); | ||
}, [initialValue]); | ||
const start = (time?: number) => { | ||
if (!startUp) setstartUp(true); | ||
|
||
useEffect(() => { | ||
if (autoStart) trigger(); | ||
|
||
return () => { | ||
stop(); | ||
}; | ||
}, [stop, trigger, autoStart]); | ||
|
||
return { | ||
value: timer, | ||
stop, | ||
trigger, | ||
reset, | ||
isFinished | ||
if (time === undefined) { | ||
if (count === 0) return; | ||
setInitialCount(plus(count)); | ||
} else { | ||
setInitialCount(plus(time)); | ||
} | ||
}; | ||
|
||
const stop = () => { | ||
clearTimer(); | ||
}; | ||
|
||
const reset = () => { | ||
if (!autoStart) setstartUp(false); | ||
setInitialCount(plus(initialTime)); | ||
}; | ||
|
||
useEffect(() => { | ||
setCount(minus(initialCount)); | ||
if (startUp) timer(); | ||
|
||
return () => clearTimer(); | ||
}, [initialCount, timer, startUp]); | ||
|
||
return [count, { start, stop, reset }]; | ||
}; |
Oops, something went wrong.