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:
- If
debounce/throttletime is shorter than the API response time: the async task may be called again before it finishes. - If
debounce/throttletime is longer than the API response time: the async task has finished, but elements like buttons may still be disabled. - When you want immediate feedback:
debounce/throttledelays calls, making it limited for showing immediate responses to the user. - Because
debounce/throttleis time-based, it focuses on how often something runs rather than whether duplicate calls occur.
Use useBlockMultipleAsyncCalls to address these limitations.
Code
Interface
interface UseBlockMultipleAsyncCallsReturnType {
isError: boolean;
isLoading: boolean;
blockMultipleAsyncCalls: <T, Args extends unknown[]>(
callback: (...args: Args) => Promise<T>
) => (...args: Args) => Promise<T | undefined>;
}
function useBlockMultipleAsyncCalls(): UseBlockMultipleAsyncCallsReturnType
Returns
| Name | Type | Description |
|---|---|---|
isLoading | boolean | Whether an async call is currently in progress. |
isError | boolean | Whether 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.
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.
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
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.
Example2 (isError handling)
When an error occurs, isError becomes true and resets to false on the next successful call.
Click a button to see logs.