Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4813e2b
feat: add leftTime for useCountDown
li-jia-nan Jul 5, 2022
fd8750e
fix: test
li-jia-nan Jul 5, 2022
c65dd3d
fix: optimize
li-jia-nan Jul 6, 2022
90d3e20
fix: fix timer
li-jia-nan Jul 6, 2022
c1c26c4
Merge branch 'alibaba:master' into master
li-jia-nan Jul 6, 2022
ff337ed
fix: code fallback
li-jia-nan Jul 6, 2022
886e8d8
Update index.test.ts
li-jia-nan Jul 6, 2022
138dc02
Merge branch 'master' into master
crazylxr Jul 7, 2022
320f2bf
test: add test
li-jia-nan Jul 7, 2022
7648d9e
style: optimize
li-jia-nan Jul 7, 2022
4a75768
style: optimize
li-jia-nan Jul 7, 2022
14e90e1
Update index.test.ts
li-jia-nan Jul 7, 2022
2e4a3fb
fix: fix test
li-jia-nan Jul 7, 2022
74c764b
fix: fix test
li-jia-nan Jul 7, 2022
e3cbcb5
Update index.test.ts
li-jia-nan Jul 7, 2022
9d6ff4b
Merge branch 'alibaba:master' into master
li-jia-nan Jul 10, 2022
a189f36
fix: Compatible with The value of leftTime is 0
li-jia-nan Jul 10, 2022
114c73b
Merge branch 'alibaba:master' into master
li-jia-nan Jul 12, 2022
23df0ed
Merge branch 'alibaba:master' into master
li-jia-nan Jul 12, 2022
2811f09
refactor: rewrite calcLeft
li-jia-nan Jul 12, 2022
4ec1915
refactor: rewrite calcLeft
li-jia-nan Jul 12, 2022
957d8c1
Merge branch 'alibaba:master' into master
li-jia-nan Jul 18, 2022
3dde897
Merge branch 'alibaba:master' into master
li-jia-nan Jul 19, 2022
b63ac37
fix: rewrite calcLeft
li-jia-nan Jul 19, 2022
ab0c201
fix: optimize startTime
li-jia-nan Jul 19, 2022
c7840d7
Merge branch 'alibaba:master' into master
li-jia-nan Jul 20, 2022
3cf7e71
fix: ci/cd
li-jia-nan Jul 20, 2022
6e44b21
fix: ci/cd
li-jia-nan Jul 20, 2022
bcf7e13
Merge branch 'alibaba:master' into master
li-jia-nan Jul 21, 2022
2900b51
fix: optimize isValidTime
li-jia-nan Jul 21, 2022
5eaeb5f
fix: code optimize
li-jia-nan Jul 21, 2022
f9e6dc1
fix: strong type
li-jia-nan Jul 21, 2022
8583e17
fix: remove isVaildTime
li-jia-nan Jul 21, 2022
49f5d9b
fix: code optimize
li-jia-nan Jul 21, 2022
588d94a
fix: code optimize
li-jia-nan Jul 21, 2022
c3e9d57
Merge branch 'alibaba:master' into master
li-jia-nan Jul 21, 2022
08eb6cf
fix: fix cicd
li-jia-nan Jul 21, 2022
52e34f3
fix: fix cicd
li-jia-nan Jul 21, 2022
7d2b15f
fix: useCountdown typo fix
brickspert Jul 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions packages/hooks/src/useCountDown/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,105 @@ describe('useCountDown', () => {
});
expect(result.current[0]).toBe(0);
});

it('should initialize correctly with undefined leftTime', () => {
const { result } = setup();

const [count, formattedRes] = result.current;

expect(count).toBe(0);
expect(formattedRes).toEqual({
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
});
});

it('should initialize correctly with correct leftTime', () => {
const { result } = setup({ leftTime: 5 * 1000, interval: 1000 });
const [count, formattedRes] = result.current;
expect(count).toBe(5000);
expect(formattedRes.seconds).toBe(5);
expect(formattedRes.milliseconds).toBe(0);
});

it('should work manually', () => {
const { result, rerender } = setup({ interval: 100 });

rerender({ leftTime: 5 * 1000, interval: 1000 });
expect(result.current[0]).toBe(5000);
expect(result.current[1].seconds).toBe(5);

act(() => {
jest.advanceTimersByTime(1000);
});
expect(result.current[0]).toBe(4000);
expect(result.current[1].seconds).toBe(4);

act(() => {
jest.advanceTimersByTime(4000);
});
expect(result.current[0]).toEqual(0);
expect(result.current[1].seconds).toBe(0);

act(() => {
jest.advanceTimersByTime(1000);
});

expect(result.current[0]).toEqual(0);
expect(result.current[1].seconds).toBe(0);
});

it('should work automatically', () => {
const { result } = setup({ leftTime: 5 * 1000, interval: 1000 });

expect(result.current[0]).toBe(5000);
expect(result.current[1].seconds).toBe(5);

act(() => {
jest.advanceTimersByTime(1000);
});
expect(result.current[0]).toBe(4000);
expect(result.current[1].seconds).toBe(4);

act(() => {
jest.advanceTimersByTime(4000);
});
expect(result.current[0]).toBe(0);
expect(result.current[1].seconds).toBe(0);
});

it('should work stop', () => {
const { result, rerender } = setup({ leftTime: 5 * 1000, interval: 1000 });

rerender({ leftTime: 5 * 1000, interval: 1000 });
expect(result.current[0]).toBe(5000);
expect(result.current[1].seconds).toBe(5);

act(() => {
jest.advanceTimersByTime(1000);
});
expect(result.current[0]).toBe(4000);
expect(result.current[1].seconds).toBe(4);

rerender({ leftTime: undefined });
expect(result.current[0]).toBe(0);
expect(result.current[1].seconds).toBe(0);
});

it('it onEnd should work', () => {
const onEnd = jest.fn();
setup({ leftTime: 5 * 1000, interval: 1000, onEnd });
act(() => {
jest.advanceTimersByTime(6000);
});
expect(onEnd).toBeCalled();
});

it('timeLeft should be 0 when leftTime less than current time', () => {
const { result } = setup({ leftTime: -5 * 1000 });
expect(result.current[0]).toBe(0);
});
});
17 changes: 17 additions & 0 deletions packages/hooks/src/useCountDown/demo/demo3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* title: The rest of time
* desc: A countdown to the number of milliseconds remaining.
*
* title.zh-CN: 剩余时间
* desc.zh-CN: 剩余时间毫秒数的倒计时
*/

import React from 'react';
import { useCountDown } from 'ahooks';

const App: React.FC = () => {
const [countdown] = useCountDown({ leftTime: 60 * 1000 });
return <p>{countdown}</p>;
};

export default App;
10 changes: 9 additions & 1 deletion packages/hooks/src/useCountDown/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ A hook for manage countdown.

<code src="./demo/demo2.tsx" />

## Config leftTime

<code src="./demo/demo3.tsx" />

## API

```typescript
Expand All @@ -30,6 +34,7 @@ interface FormattedRes {

const [countdown, formattedRes] = useCountDown(
{
leftTime,
targetDate,
interval,
onEnd
Expand All @@ -46,10 +51,13 @@ The precision of useCountDown is milliseconds, which may cause the following pro

If you only need to be accurate to the second, you can use it like this `Math.round(countdown / 1000)`.

If both `leftTime` and `targetDate` are passed, the `targetDate` is ignored, the `leftTime` is dominant.

### Params

| Property | Description | Type | Default |
| ---------- | -------------------------------------------- | ------------ | ------- |
| leftTime | The rest of time, in milliseconds | `number` | - |
| targetDate | Target time | `TDate` | - |
| interval | Time interval between ticks, in milliseconds | `number` | `1000` |
| onEnd | Function to call when countdown completes | `() => void` | - |
Expand All @@ -63,4 +71,4 @@ If you only need to be accurate to the second, you can use it like this `Math.ro

## Remark

`targetDate`、`interval`、`onEnd` support dynamic change.
`leftTime`、`targetDate`、`interval`、`onEnd` support dynamic change.
45 changes: 25 additions & 20 deletions packages/hooks/src/useCountDown/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import dayjs from 'dayjs';
import { useEffect, useMemo, useState } from 'react';
import useLatest from '../useLatest';
import { isNumber } from '../utils/index';

export type TDate = Date | number | string | undefined;
export type TDate = dayjs.ConfigType;

export type Options = {
export interface Options {
leftTime?: number;
targetDate?: TDate;
interval?: number;
onEnd?: () => void;
};
}

export interface FormattedRes {
days: number;
Expand All @@ -18,16 +20,13 @@ export interface FormattedRes {
milliseconds: number;
}

const calcLeft = (t?: TDate) => {
if (!t) {
const calcLeft = (target?: TDate) => {
if (!target) {
return 0;
}
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
const left = dayjs(t).valueOf() - new Date().getTime();
if (left < 0) {
return 0;
}
return left;
const left = dayjs(target).valueOf() - Date.now();
return left < 0 ? 0 : left;
};

const parseMs = (milliseconds: number): FormattedRes => {
Expand All @@ -40,25 +39,33 @@ const parseMs = (milliseconds: number): FormattedRes => {
};
};

const useCountdown = (options?: Options) => {
const { targetDate, interval = 1000, onEnd } = options || {};
const useCountdown = (options: Options = {}) => {
const { leftTime, targetDate, interval = 1000, onEnd } = options || {};

const target = useMemo<TDate>(() => {
if ('leftTime' in options) {
return isNumber(leftTime) && leftTime > 0 ? Date.now() + leftTime : undefined;
} else {
return targetDate;
}
}, [leftTime, targetDate]);

const [timeLeft, setTimeLeft] = useState(() => calcLeft(targetDate));
const [timeLeft, setTimeLeft] = useState(() => calcLeft(target));

const onEndRef = useLatest(onEnd);

useEffect(() => {
if (!targetDate) {
if (!target) {
// for stop
setTimeLeft(0);
return;
}

// 立即执行一次
setTimeLeft(calcLeft(targetDate));
setTimeLeft(calcLeft(target));

const timer = setInterval(() => {
const targetLeft = calcLeft(targetDate);
const targetLeft = calcLeft(target);
setTimeLeft(targetLeft);
if (targetLeft === 0) {
clearInterval(timer);
Expand All @@ -67,11 +74,9 @@ const useCountdown = (options?: Options) => {
}, interval);

return () => clearInterval(timer);
}, [targetDate, interval]);
}, [target, interval]);

const formattedRes = useMemo(() => {
return parseMs(timeLeft);
}, [timeLeft]);
const formattedRes = useMemo(() => parseMs(timeLeft), [timeLeft]);

return [timeLeft, formattedRes] as const;
};
Expand Down
10 changes: 9 additions & 1 deletion packages/hooks/src/useCountDown/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ nav:

<code src="./demo/demo2.tsx" />

## 通过 leftTime 配置剩余时间

<code src="./demo/demo3.tsx" />

**说明**

useCountDown 的精度为毫秒,可能会造成以下几个问题
Expand All @@ -24,6 +28,8 @@ useCountDown 的精度为毫秒,可能会造成以下几个问题

如果你的精度只要到秒就好了,可以这样用 `Math.round(countdown / 1000)`。

如果同时传了 `leftTime` 和 `targetDate`,则会忽略 `targetDate`,以 `leftTime` 为主

## API

```typescript
Expand All @@ -39,6 +45,7 @@ interface FormattedRes {

const [countdown, formattedRes] = useCountDown(
{
leftTime,
targetDate,
interval,
onEnd
Expand All @@ -50,6 +57,7 @@ const [countdown, formattedRes] = useCountDown(

| 参数 | 说明 | 类型 | 默认值 |
| ---------- | -------------------- | ------------ | ------ |
| leftTime | 剩余时间(毫秒) | `number` | - |
| targetDate | 目标时间 | `TDate` | - |
| interval | 变化时间间隔(毫秒) | `number` | `1000` |
| onEnd | 倒计时结束触发 | `() => void` | - |
Expand All @@ -63,4 +71,4 @@ const [countdown, formattedRes] = useCountDown(

## 备注

`targetDate`、`interval`、`onEnd` 支持动态变化
`leftTime`、`targetDate`、`interval`、`onEnd` 支持动态变化