Skip to main content

useBlockMultipleAsyncCalls

A custom hook that prevents duplicate async calls when one is already in progress, and provides isLoading and isError state.

debounce/throttle is effective in most cases for preventing duplicate function calls. However, debounce/throttle does not guarantee async task completion, so it has the following limitations:

  1. If debounce/throttle time is shorter than the API response time: the async task may be called again before it finishes.
  2. If debounce/throttle time is longer than the API response time: the async task has finished, but elements like buttons may still be disabled.
  3. When you want immediate feedback: debounce/throttle delays calls, making it limited for showing immediate responses to the user.
  4. Because debounce/throttle is time-based, it focuses on how often something runs rather than whether duplicate calls occur.

Use useBlockMultipleAsyncCalls to address these limitations.


Code

🔗 View source code


Interface

typescript
interface UseBlockMultipleAsyncCallsReturnType {
isError: boolean;
isLoading: boolean;
blockMultipleAsyncCalls: <T, Args extends unknown[]>(
callback: (...args: Args) => Promise<T>
) => (...args: Args) => Promise<T | undefined>;
}
typescript
function useBlockMultipleAsyncCalls(): UseBlockMultipleAsyncCallsReturnType

Returns

NameTypeDescription
isLoadingbooleanWhether an async call is currently in progress.
isErrorbooleanWhether an error occurred during the async call. Resets to false on the next successful call.
blockMultipleAsyncCalls<T, Args>(callback: (...args: Args) => Promise<T>) => (...args: Args) => Promise<T | undefined>Wraps a callback function to prevent duplicate calls while one is in progress.

Usage

Basic Usage

blockMultipleAsyncCalls(callback) returns a wrapped async function. You can use the returned function directly as an event handler.

typescript
import { useBlockMultipleAsyncCalls } from '@modern-kit/react';

const Component = () => {
const { isLoading, isError, blockMultipleAsyncCalls } = useBlockMultipleAsyncCalls();

const fetchData = async () => {
const data = await fetch('/api/data').then((res) => res.json());
// process data
};

const handleClick = blockMultipleAsyncCalls(fetchData);

return (
<div>
<button onClick={handleClick} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Fetch data'}
</button>
{isError && <p>An error occurred.</p>}
</div>
);
};

Passing Arguments

You can pass arguments to the callback function.

typescript
import { useBlockMultipleAsyncCalls } from '@modern-kit/react';

const Component = () => {
const { isLoading, blockMultipleAsyncCalls } = useBlockMultipleAsyncCalls();

const fetchUser = async (userId: number) => {
const user = await fetch(`/api/users/${userId}`).then((res) => res.json());
// process user data
};

return (
<button onClick={() => blockMultipleAsyncCalls(fetchUser)(42)} disabled={isLoading}>
Fetch user
</button>
);
};

Using with @tanstack/react-query

typescript
import { useBlockMultipleAsyncCalls } from '@modern-kit/react';
import { useQuery } from '@tanstack/react-query';

const Component = () => {
const { blockMultipleAsyncCalls } = useBlockMultipleAsyncCalls();

const fetchUser = async ({ id }: { id: number }) => {
const user = await fetch(`/api/users/${id}`).then((res) => res.json());
return user;
};

const mutation = useMutation({
mutationFn: blockMultipleAsyncCalls(fetchUser),
});

return <button onClick={() => mutation.mutate({ id: 42 })}>Fetch user</button>;
};

Example

Example1 (Basic usage)

Try clicking the button rapidly multiple times. While isLoading is true, duplicate requests are blocked and the button is disabled.

Even if you click rapidly multiple times, the request runs only once until the in-progress request completes.

Button click count
0
Actual API call count
0

Example2 (isError handling)

When an error occurs, isError becomes true and resets to false on the next successful call.

isLoading: false
isError: false

Click a button to see logs.