Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(useInterval): add useInterval composable #343

Merged
merged 8 commits into from
Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Check out the [examples folder](examples) or start hacking on [codesandbox](http
- [sharedRef](composable/misc/sharedRef) - cross-tab reactive `ref`
- [VModel](composable/misc/vmodel) - helper to wrap model update into a `ref` `[vue3 only]`
- [injectFactory](composable/misc/injectFactory) - same as [inject](https://vue-composition-api-rfc.netlify.app/api.html#dependency-injection) but allows you to have a factory as default value
- [interval](composable/misc/interval) - self-remove `setInterval` on unmount
- [lockScroll](composable/misc/lockScroll) - `lock-scroll` component

### Storage
Expand Down
44 changes: 44 additions & 0 deletions docs/composable/misc/interval.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Interval

> `setInterval` with `start`/`remove` and `clearInterval` on unmounted.

## Parameters

```js
import { useInterval } from "vue-composable";

useInterval(callback, ms?, ...args);
```

| Parameters | Type | Required | Default | Description |
| ---------- | ---------------------------- | -------- | ----------- | ---------------------------------------------------------------------------- |
| callback | `(...args):void` | `true` | | `setInterval` callback |
| ms | `number | false | undefined` | `false` | `undefined` | callback interval `ms`, if `ms` provided it will `setInterval` automatically |
| ...args | `any` | `false` | `[]` | callback args |

## Methods

The `useInterval` function exposes the following methods:

```js
import { useInterval } from "vue-composable";

const { start, remove } = useInterval();
```

| Signature | Description |
| --------- | ------------------------ |
| start | Start |
| remove | Manually `clearInterval` |

### Code

```vue
<script>
export default {
setup() {
useInterval(() => console.log(Date.now()), 1000);
}
};
</script>
```
1 change: 1 addition & 0 deletions packages/vue-composable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Check our [documentation](https://pikax.me/vue-composable/)
- [sharedRef](https://pikax.me/vue-composable/composable/misc/sharedRef) - cross-tab reactive `ref`
- [VModel](https://pikax.me/vue-composable/composable/meta/vmodel) - helper to wrap model update into a `ref` `[vue3 only]`
- [injectFactory](https://pikax.me/vue-composable/composable/misc/injectFactory) - same as [inject](https://vue-composition-api-rfc.netlify.app/api.html#dependency-injection) but allows you to have a factory as default value
- [interval](https://pikax.me/vue-composable/composable/misc/interval) - self-remove `setInterval` on unmount
- [lockScroll](https://pikax.me/vue-composable/composable/misc/lockScroll) - `lock-scroll` component

### Storage
Expand Down
158 changes: 158 additions & 0 deletions packages/vue-composable/__tests__/misc/interval.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { createVue } from "../utils";
import {
useInterval,
UseIntervalReturn,
UseIntervalReturnMs,
UseIntervalReturnArgs
} from "../../src";

describe("interval", () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
it("should work", async () => {
const callback = jest.fn();
const ms = 100;

let interval: UseIntervalReturn & UseIntervalReturnMs = {} as any;
const { mount, destroy } = createVue({
template: `<div></div>`,
setup() {
interval = useInterval(callback, ms);
}
});

mount();

expect(clearInterval).not.toHaveBeenCalled();
expect(setInterval).toHaveBeenCalledWith(callback, ms);

interval.start();
expect(clearInterval).toHaveBeenCalled();
expect(setInterval).toHaveBeenNthCalledWith(2, callback, ms);

interval.remove();

expect(clearInterval).toHaveBeenCalledTimes(2);

interval.start();

destroy();

expect(clearInterval).toHaveBeenCalledTimes(3);
});

it("should remove at unmount", () => {
const callback = jest.fn();
const ms = 100;

const { mount, destroy } = createVue({
template: `<div></div>`,
setup() {
useInterval(callback, ms);
}
});

mount();

expect(clearInterval).not.toHaveBeenCalled();
expect(setInterval).toHaveBeenCalledWith(callback, ms);

destroy();

expect(clearInterval).toHaveBeenCalled();
});

it("should not start if ms are not passed", () => {
const callback = jest.fn();
const ms = 100;

let interval: UseIntervalReturn = {} as any;
const { mount, destroy } = createVue({
template: `<div></div>`,
setup() {
interval = useInterval(callback);
}
});

mount();

expect(clearInterval).not.toHaveBeenCalled();
expect(setInterval).not.toHaveBeenCalled();

interval.start(ms);
expect(setInterval).toHaveBeenCalledWith(callback, ms);

destroy();
});

it("should override ms on start()", () => {
const callback = jest.fn();
const ms = 100;

let interval: UseIntervalReturn & UseIntervalReturnMs = {} as any;
const { mount, destroy } = createVue({
template: `<div></div>`,
setup() {
interval = useInterval(callback, ms);
}
});

mount();

expect(clearInterval).not.toHaveBeenCalled();
expect(setInterval).toHaveBeenCalled();

interval.start(20);
expect(setInterval).toHaveBeenLastCalledWith(callback, 20);

destroy();
});

it("should override args on start()", () => {
const callback = jest.fn((x: number) => x);
const ms = 100;

let interval: UseIntervalReturn &
UseIntervalReturnArgs<[number]> = {} as any;
const { mount, destroy } = createVue({
template: `<div></div>`,
setup() {
interval = useInterval(callback, ms, 1);
}
});

mount();

expect(clearInterval).not.toHaveBeenCalled();
expect(setInterval).toHaveBeenLastCalledWith(callback, ms, 1);

interval.start(undefined, 2);
expect(setInterval).toHaveBeenLastCalledWith(callback, ms, 2);

destroy();
});

it("should not start if no ms passed", () => {
const callback = jest.fn((x: number) => x);
let interval: UseIntervalReturnMs = {} as any;
const { mount, destroy } = createVue({
template: `<div></div>`,
setup() {
// @ts-ignore
interval = useInterval(callback);
}
});

mount();

expect(clearInterval).not.toHaveBeenCalled();
interval.start();
expect(setInterval).not.toHaveBeenCalled();

destroy();
});
});
1 change: 1 addition & 0 deletions packages/vue-composable/src/misc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./sharedRef";
export * from "./lockScroll";
export * from "./vmodel";
export * from "./injectFactory";
export * from "./interval";
76 changes: 76 additions & 0 deletions packages/vue-composable/src/misc/interval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { onUnmounted } from "../api";
import { isNumber } from "../utils";

export interface UseIntervalReturn<TArgs extends Array<any> = []> {
start(ms: number, ...args: TArgs): number;

remove(): void;
}

export interface UseIntervalReturnMs {
start(): number;
}

export interface UseIntervalReturnArgs<TArgs extends Array<any>> {
start(_: undefined, ...args: TArgs): number;
}

export function useInterval<TArgs extends Array<any>>(
callback: (...args: TArgs) => void,
ms?: false,
...args: TArgs
): UseIntervalReturn<TArgs>;

export function useInterval<TArgs extends Array<any>>(
callback: (...args: TArgs) => void,
ms?: false
): UseIntervalReturn<TArgs>;

export function useInterval<TArgs extends Array<any>>(
callback: (...args: TArgs) => void,
ms: number,
...args: TArgs
): UseIntervalReturn<TArgs> &
UseIntervalReturnMs &
UseIntervalReturnArgs<TArgs>;

export function useInterval<TArgs extends Array<any>>(
callback: (...args: TArgs) => void,
ms?: number | false,
...args: TArgs
): UseIntervalReturn<TArgs> &
UseIntervalReturnMs &
UseIntervalReturnArgs<TArgs> {
let intervalId: number | undefined = undefined;

const start = (_ms?: number, ..._args: any[]) => {
remove();
if (!_ms && !ms) {
return;
}
const m = (_ms || ms) as number;
return (intervalId = setInterval(
callback,
m,
...(_args && _args.length ? _args : args)
) as any);
};

const remove = () => {
if (!intervalId) return;
clearInterval(intervalId);
intervalId = undefined;
};

if (isNumber(ms)) {
start();
}

onUnmounted(remove);
return { remove, start };
}

// TODO move this: type checkings
// useInterval((x: number) => {}).start();
// useInterval((x: number) => {}, 100, 1).start();
// useInterval(() => {}).start();
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Check our [documentation](https://pikax.me/vue-composable/)
- [sharedRef](https://pikax.me/vue-composable/composable/misc/sharedRef) - cross-tab reactive `ref`
- [VModel](https://pikax.me/vue-composable/composable/meta/vmodel) - helper to wrap model update into a `ref` `[vue3 only]`
- [injectFactory](https://pikax.me/vue-composable/composable/misc/injectFactory) - same as [inject](https://vue-composition-api-rfc.netlify.app/api.html#dependency-injection) but allows you to have a factory as default value
- [interval](https://pikax.me/vue-composable/composable/misc/interval) - self-remove `setInterval` on unmount
- [lockScroll](https://pikax.me/vue-composable/composable/misc/lockScroll) - `lock-scroll` component

### Storage
Expand Down