diff --git a/README.md b/README.md
index 8e36339e..a41eeb41 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,7 @@
- [**Animations**](./docs/Animations.md)
- [`useRaf`](./docs/useRaf.md) — re-renders component on each `requestAnimationFrame`.
- - [`useInterval`](./docs/useInterval.md) — re-renders component on a set interval using `setInterval`.
+ - [`useInterval`](./docs/useInterval.md) and [`useHarmonicIntervalFn`](./docs/useHarmonicIntervalFn.md) — re-renders component on a set interval using `setInterval`.
- [`useSpring`](./docs/useSpring.md) — interpolates number over time according to spring dynamics.
- [`useTimeout`](./docs/useTimeout.md) — re-renders component after a timeout.
- [`useTimeoutFn`](./docs/useTimeoutFn.md) — calls given function after a timeout. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/animation-usetimeoutfn--demo)
diff --git a/docs/useHarmonicIntervalFn.md b/docs/useHarmonicIntervalFn.md
new file mode 100644
index 00000000..47e3620f
--- /dev/null
+++ b/docs/useHarmonicIntervalFn.md
@@ -0,0 +1,14 @@
+# `useHarmonicIntervalFn`
+
+Same as [`useInterval`](./useInterval.md) hook, but triggers all effects with the same delay
+at the same time.
+
+For example, this allows you to create ticking clocks on the page which re-render second counter
+all at the same time.
+
+
+## Reference
+
+```js
+useHarmonicIntervalFn(fn, delay?: number)
+```
diff --git a/package.json b/package.json
index 3502ce15..c112998b 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"react-fast-compare": "^2.0.4",
"react-wait": "^0.3.0",
"screenfull": "^4.1.0",
+ "set-harmonic-interval": "^1.0.0",
"throttle-debounce": "^2.0.1",
"ts-easing": "^0.2.0"
},
diff --git a/src/__stories__/useHarmonicIntervalFn.story.tsx b/src/__stories__/useHarmonicIntervalFn.story.tsx
new file mode 100644
index 00000000..0a0c32fc
--- /dev/null
+++ b/src/__stories__/useHarmonicIntervalFn.story.tsx
@@ -0,0 +1,69 @@
+import { storiesOf } from '@storybook/react';
+import * as React from 'react';
+import { useHarmonicIntervalFn, useInterval, useTimeoutFn } from '..';
+import ShowDocs from './util/ShowDocs';
+
+const Clock: React.FC<{ useInt: typeof useHarmonicIntervalFn }> = ({ useInt }) => {
+ const [count, setCount] = React.useState(0);
+ useInt(() => {
+ setCount(cnt => cnt + 1);
+ }, 1000);
+
+ let m: number | string = Math.floor(count / 60);
+ let s: number | string = count % 60;
+
+ m = m < 10 ? '0' + m : String(m);
+ s = s < 10 ? '0' + s : String(s);
+
+ const style: React.CSSProperties = {
+ padding: '20px 5px',
+ border: '1px solid #fafafa',
+ float: 'left',
+ fontFamily: 'monospace',
+ };
+
+ return
{m + ':' + s}
;
+};
+
+const Demo: React.FC<{ useInt: typeof useHarmonicIntervalFn }> = ({ useInt }) => {
+ const [showSecondClock, setShowSecondClock] = React.useState(false);
+ useTimeoutFn(() => {
+ setShowSecondClock(true);
+ }, 500);
+
+ const headingStyle: React.CSSProperties = {
+ fontFamily: 'sans',
+ fontSize: '20px',
+ padding: '0',
+ lineHeight: '1.5em',
+ };
+
+ const rowStyle: React.CSSProperties = {
+ width: '100%',
+ clear: 'both',
+ };
+
+ return (
+ <>
+
+
{useInt.name}
+
+ {showSecondClock ? : null}
+
+ >
+ );
+};
+
+storiesOf('Animation|useHarmonicIntervalFn', module)
+ .add('Docs', () => )
+ .add('Demo', () => (
+ <>
+
+
+
+
+
+
+
+ >
+ ));
diff --git a/src/__stories__/useInterval.story.tsx b/src/__stories__/useInterval.story.tsx
index e5cd0f16..7ac3d37c 100644
--- a/src/__stories__/useInterval.story.tsx
+++ b/src/__stories__/useInterval.story.tsx
@@ -28,6 +28,6 @@ const Demo = () => {
);
};
-storiesOf('Side effects|useInterval', module)
+storiesOf('Animation|useInterval', module)
.add('Docs', () => )
.add('Demo', () => );
diff --git a/src/index.ts b/src/index.ts
index 644f587e..589c353f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -23,6 +23,7 @@ export { default as useFullscreen } from './useFullscreen';
export { default as useGeolocation } from './useGeolocation';
export { default as useGetSet } from './useGetSet';
export { default as useGetSetState } from './useGetSetState';
+export { default as useHarmonicIntervalFn } from './useHarmonicIntervalFn';
export { default as useHover } from './useHover';
export { default as useHoverDirty } from './useHoverDirty';
export { default as useIdle } from './useIdle';
diff --git a/src/useHarmonicIntervalFn.ts b/src/useHarmonicIntervalFn.ts
new file mode 100644
index 00000000..d591a32d
--- /dev/null
+++ b/src/useHarmonicIntervalFn.ts
@@ -0,0 +1,20 @@
+import { useEffect, useRef } from 'react';
+import { setHarmonicInterval, clearHarmonicInterval } from 'set-harmonic-interval';
+
+const useHarmonicIntervalFn = (fn: Function, delay: number | null = 0) => {
+ const latestCallback = useRef(() => {});
+
+ useEffect(() => {
+ latestCallback.current = fn;
+ });
+
+ useEffect(() => {
+ if (delay !== null) {
+ const interval = setHarmonicInterval(() => latestCallback.current(), delay);
+ return () => clearHarmonicInterval(interval);
+ }
+ return undefined;
+ }, [delay]);
+};
+
+export default useHarmonicIntervalFn;
diff --git a/yarn.lock b/yarn.lock
index aa473406..d46a2730 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11062,6 +11062,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+set-harmonic-interval@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.0.tgz#2759658395579fc336b9cd026eefe1ad9e742f9b"
+ integrity sha512-RJtrhB5G10e5A1auBv/jHGq0KWfHH8PAb4ln4+kjiHS46aAP12rSdarSj9GDlxQ3QULA2pefEDpm9Y1Xnz+eng==
+
set-value@^0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1"