Skip to content

Commit

Permalink
feat: add useInterval hook (#58)
Browse files Browse the repository at this point in the history
Is a custom hook to create and manage intervals
  • Loading branch information
immois authored Oct 5, 2023
1 parent 6dc04e9 commit cce3f8c
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/quick-chefs-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@raddix/use-interval': major
---

Added the useInterval hook
5 changes: 5 additions & 0 deletions packages/interactions/use-interval/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# useInterval

The `useInterval` hook has an excellent API for creating and managing intervals in a simple and efficient way.

Please refer to the [documentation](https://www.raddix.website/docs/interactions/use-interval) for more information.
50 changes: 50 additions & 0 deletions packages/interactions/use-interval/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@raddix/use-interval",
"description": "A React hook for setting an interval.",
"version": "0.1.0",
"license": "MIT",
"main": "src/index.ts",
"author": "Moises Machuca Valverde <rolan.machuca@gmail.com> (https://www.moisesmachuca.com)",
"homepage": "https://www.raddix.website",
"repository": {
"type": "git",
"url": "https://github.com/gdvu/raddix.git"
},
"keywords": [
"react-hooks",
"react-use-interval",
"react-interval-hook",
"interval",
"interval-hook",
"use-interval",
"use-interval-hook",
"use-interval-hook-react",
"use-interval-react",
"setInterval"
],
"sideEffects": false,
"scripts": {
"lint": "eslint \"{src,tests}/*.{ts,tsx,css}\"",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
"build": "tsup src --dts",
"prepack": "clean-package",
"postpack": "clean-package restore"
},
"files": [
"dist",
"README.md"
],
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"clean-package": "../../../clean-package.config.json",
"tsup": {
"clean": true,
"target": "es2019",
"format": [
"cjs",
"esm"
]
}
}
43 changes: 43 additions & 0 deletions packages/interactions/use-interval/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useCallback, useEffect, useRef } from 'react';

export interface IntervalResult {
/** Clear interval and stop the execution */
clear: () => void;
/** Execute the interval */
run: () => void;
}

type UseInterval = (
callback: () => void,
delay: number,
inmediate?: boolean
) => IntervalResult;

export const useInterval: UseInterval = (callback, delay, inmediate = true) => {
const savedCallback = useRef(callback);
const id = useRef<NodeJS.Timeout | null>(null);

const clearId = () => {
if (!id.current) return;
clearInterval(id.current);
id.current = null;
};

const run = useCallback(() => {
if (id.current) return;
const tick = () => savedCallback.current();

id.current = setInterval(tick, delay);
}, [delay]);

useEffect(() => {
savedCallback.current = callback;
}, [callback]);

useEffect(() => {
if (inmediate) run();
return () => clearId();
}, [run, inmediate]);

return { clear: clearId, run };
};
91 changes: 91 additions & 0 deletions packages/interactions/use-interval/tests/use-interval.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { renderHook } from '@testing-library/react';
import { useInterval } from '../src';

describe('useInterval test:', () => {
beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.useRealTimers();
});

it('the interval should work', () => {
const callback = jest.fn();
renderHook(() => useInterval(callback, 1000));

expect(callback).not.toBeCalled();
jest.advanceTimersByTime(2000);
expect(callback).toHaveBeenCalledTimes(2);
});

it('should clear the interval when the component unmounts', () => {
const callback = jest.fn();
const { unmount } = renderHook(() => useInterval(callback, 1000));

expect(callback).not.toBeCalled();
unmount();
jest.advanceTimersByTime(2000);
expect(callback).not.toBeCalled();
});

it('should clear the interval when the delay changes', () => {
const callback = jest.fn();
const { rerender } = renderHook(
({ delay }) => useInterval(callback, delay),
{
initialProps: { delay: 1000 }
}
);

expect(callback).not.toBeCalled();
rerender({ delay: 2000 });
jest.advanceTimersByTime(1000);
expect(callback).not.toBeCalled();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
});

it('should clear the interval when the immediate changes', () => {
const callback = jest.fn();
const { rerender } = renderHook(
({ immediate }) => useInterval(callback, 1000, immediate),
{
initialProps: { immediate: true }
}
);

expect(callback).not.toBeCalled();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
rerender({ immediate: false });
jest.advanceTimersByTime(3000);
expect(callback).toHaveBeenCalledTimes(1);
});

it('should stop the interval when the clear function is called', () => {
const callback = jest.fn();
const { result } = renderHook(() => useInterval(callback, 1000));

expect(callback).not.toBeCalled();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
result.current.clear();
jest.advanceTimersByTime(2000);
expect(callback).toHaveBeenCalledTimes(1);
});

it('should run the interval when the run function is called', () => {
const callback = jest.fn();
const { result } = renderHook(() => useInterval(callback, 1000, false));

jest.advanceTimersByTime(2000);
expect(callback).not.toBeCalled();
result.current.run();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
result.current.run();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(2);
});
});
17 changes: 13 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit cce3f8c

Please sign in to comment.