diff --git a/README.md b/README.md
index 40b9fdaff..132a5bf6f 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,8 @@ import { useMountEffect } from "@react-hookz/web/esnext";
     — Makes passed function debounced, otherwise acts like `useCallback`.
   - [**`useRafCallback`**](https://react-hookz.github.io/web/?path=/docs/callback-userafcallback)
     — Makes passed function to be called within next animation frame.
+  - [**`useThrottleCallback`**](https://react-hookz.github.io/web/?path=/docs/callback-usethrottlecallback)
+    — Makes passed function throttled, otherwise acts like `useCallback`.
 
 - #### Lifecycle
 
diff --git a/src/index.ts b/src/index.ts
index 6c7df2d70..fa6b22bf0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,7 @@
 // Callback
 export { useDebounceCallback } from './useDebounceCallback/useDebounceCallback';
 export { useRafCallback } from './useRafCallback/useRafCallback';
+export { useThrottleCallback } from './useThrottleCallback/useThrottleCallback';
 
 // Livecycle
 export {
diff --git a/src/useCookieValue/useCookieValue.ts b/src/useCookieValue/useCookieValue.ts
index d0658f374..a53a52b74 100644
--- a/src/useCookieValue/useCookieValue.ts
+++ b/src/useCookieValue/useCookieValue.ts
@@ -2,7 +2,6 @@
 import { Dispatch, useCallback, useEffect } from 'react';
 import * as Cookies from 'js-cookie';
 import { useSafeState, useSyncedRef, useFirstMountState, useMountEffect } from '..';
-
 import { isBrowser } from '../util/const';
 
 const cookiesSetters = new Map<string, Set<Dispatch<string | null>>>();
diff --git a/src/useStorageValue/useStorageValue.ts b/src/useStorageValue/useStorageValue.ts
index 9483f3a3b..7eefeaf8c 100644
--- a/src/useStorageValue/useStorageValue.ts
+++ b/src/useStorageValue/useStorageValue.ts
@@ -7,14 +7,10 @@ import {
   useSyncedRef,
   useFirstMountState,
   usePrevious,
+  useMountEffect,
 } from '..';
-
 import { INextState, resolveHookState } from '../util/resolveHookState';
-
-import { useMountEffect } from '../useMountEffect/useMountEffect';
-
 import { isBrowser } from '../util/const';
-
 import { off, on } from '../util/misc';
 
 export type IUseStorageValueOptions<
diff --git a/src/useThrottleCallback/__docs__/example.stories.tsx b/src/useThrottleCallback/__docs__/example.stories.tsx
new file mode 100644
index 000000000..346ad67e9
--- /dev/null
+++ b/src/useThrottleCallback/__docs__/example.stories.tsx
@@ -0,0 +1,23 @@
+import React, { useState } from 'react';
+import { useThrottleCallback } from '../..';
+
+export const Example: React.FC = () => {
+  const [state, setState] = useState('');
+
+  const handleChange: React.ChangeEventHandler<HTMLInputElement> = useThrottleCallback(
+    (ev) => {
+      setState(ev.target.value);
+    },
+    500,
+    []
+  );
+
+  return (
+    <div>
+      <div>Below state will update no more than once every 500ms</div>
+      <br />
+      <div>The input`s value is: {state}</div>
+      <input type="text" onChange={handleChange} />
+    </div>
+  );
+};
diff --git a/src/useThrottleCallback/__docs__/story.mdx b/src/useThrottleCallback/__docs__/story.mdx
new file mode 100644
index 000000000..eac4b8740
--- /dev/null
+++ b/src/useThrottleCallback/__docs__/story.mdx
@@ -0,0 +1,37 @@
+import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks';
+import { Example } from './example.stories';
+
+<Meta title="Callback/useThrottleCallback" component={Example} />
+
+# useThrottleCallback
+
+Makes passed function throttled, otherwise acts like `useCallback`.  
+[\[What is throttling?\]](https://css-tricks.com/debouncing-throttling-explained-examples/#throttle)
+
+> The third argument is dependencies list in `useEffect` manner, passed function will be re-wrapped
+> when delay or dependencies has changed. Changed throttle callbacks still have same timeout, meaning
+> that calling new throttled function will abort previously scheduled invocation.
+
+> Throttled function is always a void function since original callback invoked later.
+
+#### Example
+
+<Canvas>
+  <Story story={Example} inline />
+</Canvas>
+
+## Reference
+
+```ts
+function useThrottleCallback<T extends unknown[]>(
+  cb: (...args: T) => unknown,
+  delay: number,
+  deps: React.DependencyList
+): (...args: T) => void;
+```
+
+- **cb** _`(...args: T) => unknown`_ - function that will be throttled.
+- **delay** _`number`_ - throttle delay.
+- **deps** _`React.DependencyList`_ - dependencies list when to update callback.
+- **noTrailing** _`boolean`_ _(default: false)_ - if noTrailing is true, callback will only execute
+  every `delay` milliseconds, otherwise, callback will be executed once, after the last call.
diff --git a/src/useThrottleCallback/__tests__/dom.ts b/src/useThrottleCallback/__tests__/dom.ts
new file mode 100644
index 000000000..78f059c29
--- /dev/null
+++ b/src/useThrottleCallback/__tests__/dom.ts
@@ -0,0 +1,112 @@
+import { renderHook } from '@testing-library/react-hooks/dom';
+import { useThrottleCallback } from '../..';
+
+describe('useThrottleCallback', () => {
+  beforeAll(() => {
+    jest.useFakeTimers();
+  });
+
+  afterAll(() => {
+    jest.useRealTimers();
+  });
+
+  it('should be defined', () => {
+    expect(useThrottleCallback).toBeDefined();
+  });
+
+  it('should render', () => {
+    const { result } = renderHook(() => {
+      useThrottleCallback(() => {}, 200, []);
+    });
+    expect(result.error).toBeUndefined();
+  });
+
+  it('should return function same length and wrapped name', () => {
+    let { result } = renderHook(() =>
+      useThrottleCallback((_a: any, _b: any, _c: any) => {}, 200, [])
+    );
+
+    expect(result.current.length).toBe(3);
+    expect(result.current.name).toBe(`anonymous__throttled__200`);
+
+    function testFn(_a: any, _b: any, _c: any) {}
+
+    result = renderHook(() => useThrottleCallback(testFn, 100, [])).result;
+
+    expect(result.current.length).toBe(3);
+    expect(result.current.name).toBe(`testFn__throttled__100`);
+  });
+
+  it('should return new callback if delay is changed', () => {
+    const { result, rerender } = renderHook(
+      ({ delay }) => useThrottleCallback(() => {}, delay, []),
+      {
+        initialProps: { delay: 200 },
+      }
+    );
+
+    const cb1 = result.current;
+    rerender({ delay: 123 });
+
+    expect(cb1).not.toBe(result.current);
+  });
+
+  it('should invoke given callback immediately', () => {
+    const cb = jest.fn();
+    const { result } = renderHook(() => useThrottleCallback(cb, 200, []));
+
+    result.current();
+    expect(cb).toHaveBeenCalledTimes(1);
+  });
+
+  it('should pass parameters to callback', () => {
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    const cb = jest.fn((_a: number, _c: string) => {});
+    const { result } = renderHook(() => useThrottleCallback(cb, 200, []));
+
+    result.current(1, 'abc');
+    expect(cb).toHaveBeenCalledWith(1, 'abc');
+  });
+
+  it('should ignore consequential calls occurred within delay, but execute last call after delay is passed', () => {
+    const cb = jest.fn();
+    const { result } = renderHook(() => useThrottleCallback(cb, 200, []));
+
+    result.current();
+    result.current();
+    result.current();
+    result.current();
+    expect(cb).toHaveBeenCalledTimes(1);
+    jest.advanceTimersByTime(199);
+    result.current();
+    expect(cb).toHaveBeenCalledTimes(1);
+    jest.advanceTimersByTime(1);
+    expect(cb).toHaveBeenCalledTimes(2);
+    result.current();
+    expect(cb).toHaveBeenCalledTimes(2);
+    jest.advanceTimersByTime(200);
+    expect(cb).toHaveBeenCalledTimes(3);
+  });
+
+  it('should drop trailing execution if `noTrailing is set to true`', () => {
+    const cb = jest.fn();
+    const { result } = renderHook(() => useThrottleCallback(cb, 200, [], true));
+
+    result.current();
+    result.current();
+    result.current();
+    result.current();
+    expect(cb).toHaveBeenCalledTimes(1);
+    jest.advanceTimersByTime(199);
+    result.current();
+    expect(cb).toHaveBeenCalledTimes(1);
+    jest.advanceTimersByTime(1);
+    expect(cb).toHaveBeenCalledTimes(1);
+    result.current();
+    result.current();
+    result.current();
+    expect(cb).toHaveBeenCalledTimes(2);
+    jest.advanceTimersByTime(200);
+    expect(cb).toHaveBeenCalledTimes(2);
+  });
+});
diff --git a/src/useThrottleCallback/__tests__/ssr.ts b/src/useThrottleCallback/__tests__/ssr.ts
new file mode 100644
index 000000000..1c489edd8
--- /dev/null
+++ b/src/useThrottleCallback/__tests__/ssr.ts
@@ -0,0 +1,41 @@
+import { renderHook } from '@testing-library/react-hooks/server';
+import { useThrottleCallback } from '../..';
+
+describe('useThrottleCallback', () => {
+  beforeAll(() => {
+    jest.useFakeTimers();
+  });
+
+  afterAll(() => {
+    jest.useRealTimers();
+  });
+
+  it('should be defined', () => {
+    expect(useThrottleCallback).toBeDefined();
+  });
+
+  it('should render', () => {
+    const { result } = renderHook(() => {
+      useThrottleCallback(() => {}, 200, []);
+    });
+    expect(result.error).toBeUndefined();
+  });
+
+  it('should invoke given callback immediately', () => {
+    const cb = jest.fn();
+    const { result } = renderHook(() => useThrottleCallback(cb, 200, []));
+
+    result.current();
+    expect(cb).toHaveBeenCalledTimes(1);
+  });
+
+  it('should pass parameters to callback', () => {
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    const cb = jest.fn((_a: number, _c: string) => {});
+    const { result } = renderHook(() => useThrottleCallback(cb, 200, []));
+
+    result.current(1, 'abc');
+    jest.advanceTimersByTime(200);
+    expect(cb).toHaveBeenCalledWith(1, 'abc');
+  });
+});
diff --git a/src/useThrottleCallback/useThrottleCallback.ts b/src/useThrottleCallback/useThrottleCallback.ts
new file mode 100644
index 000000000..dc5672d88
--- /dev/null
+++ b/src/useThrottleCallback/useThrottleCallback.ts
@@ -0,0 +1,73 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { DependencyList, useMemo, useRef } from 'react';
+import { useUnmountEffect } from '..';
+
+export interface IThrottledFunction<Args extends any[], This> {
+  (this: This, ...args: Args): void;
+}
+
+/**
+ * Makes passed function throttled, otherwise acts like `useCallback`.
+ *
+ * @param callback Function that will be throttled.
+ * @param delay Throttle delay.
+ * @param deps Dependencies list when to update callback.
+ * @param noTrailing If noTrailing is true, callback will only execute every
+ * `delay` milliseconds, otherwise, callback will be executed one final time
+ * after the last throttled-function call.
+ */
+export function useThrottleCallback<Args extends any[], This>(
+  callback: (this: This, ...args: Args) => any,
+  delay: number,
+  deps: DependencyList,
+  noTrailing = false
+): IThrottledFunction<Args, This> {
+  const timeout = useRef<ReturnType<typeof setTimeout>>();
+  const lastCall = useRef<{ args: Args; this: This }>();
+
+  useUnmountEffect(() => {
+    if (timeout.current) {
+      clearTimeout(timeout.current);
+    }
+  });
+
+  return useMemo(() => {
+    const execute = (context: This, args: Args) => {
+      lastCall.current = undefined;
+      callback.apply(context, args);
+
+      timeout.current = setTimeout(() => {
+        timeout.current = undefined;
+
+        // if trailing execution is not disabled - call callback with last
+        // received arguments and context
+        if (!noTrailing && lastCall.current) {
+          execute(lastCall.current.this, lastCall.current.args);
+
+          lastCall.current = undefined;
+        }
+      }, delay);
+    };
+
+    // eslint-disable-next-line func-names
+    const wrapped = function (this, ...args) {
+      if (timeout.current) {
+        // if we cant execute callback immediately - save its arguments and
+        // context to execute it when delay is passed
+        lastCall.current = { args, this: this };
+
+        return;
+      }
+
+      execute(this, args);
+    } as IThrottledFunction<Args, This>;
+
+    Object.defineProperties(wrapped, {
+      length: { value: callback.length },
+      name: { value: `${callback.name || 'anonymous'}__throttled__${delay}` },
+    });
+
+    return wrapped;
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [delay, noTrailing, ...deps]);
+}