Skip to content

Commit

Permalink
feat(useCountDown): new improved API (#53)
Browse files Browse the repository at this point in the history
Added a new API that provides more flexibility and also improved the countdown logic
  • Loading branch information
immois authored Aug 22, 2023
1 parent 168a5c0 commit 91f561e
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 125 deletions.
7 changes: 7 additions & 0 deletions .changeset/orange-jeans-rest.md
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.
90 changes: 90 additions & 0 deletions packages/utilities/use-count-down/README.md
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. |
116 changes: 64 additions & 52 deletions packages/utilities/use-count-down/src/index.ts
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 }];
};
Loading

0 comments on commit 91f561e

Please sign in to comment.