improvement: loading state added to buttons who interact with backend.
This commit is contained in:
50
frontend/src/lib/hooks/useAsyncAction.ts
Normal file
50
frontend/src/lib/hooks/useAsyncAction.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Runs an async action at most once at a time (sync re-entry guard via ref).
|
||||
* Use for API mutations from buttons/controls that are not the shared Button,
|
||||
* or when several controls share one pending flag.
|
||||
*/
|
||||
export function useAsyncAction() {
|
||||
const [pending, setPending] = useState(false);
|
||||
const pendingRef = useRef(false);
|
||||
|
||||
const run = useCallback(async <T,>(fn: () => Promise<T>): Promise<T | undefined> => {
|
||||
if (pendingRef.current) return undefined;
|
||||
pendingRef.current = true;
|
||||
setPending(true);
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
pendingRef.current = false;
|
||||
setPending(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { pending, run };
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as useAsyncAction, but tracks which row/id is in flight so list UIs
|
||||
* can disable sibling actions while one mutation runs.
|
||||
*/
|
||||
export function useAsyncActionById() {
|
||||
const [pendingId, setPendingId] = useState<string | null>(null);
|
||||
const pendingIdRef = useRef<string | null>(null);
|
||||
|
||||
const run = useCallback(async <T,>(id: string, fn: () => Promise<T>): Promise<T | undefined> => {
|
||||
if (pendingIdRef.current) return undefined;
|
||||
pendingIdRef.current = id;
|
||||
setPendingId(id);
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
pendingIdRef.current = null;
|
||||
setPendingId(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { pendingId, run };
|
||||
}
|
||||
Reference in New Issue
Block a user