( callback: ( ...args: A ) => Promise<R> )
| 34 | * ``` |
| 35 | */ |
| 36 | export const useAsyncCallback = <A extends Array<unknown>, R>( |
| 37 | callback: ( ...args: A ) => Promise<R> |
| 38 | ): AsyncCallbackHookResult<A, R> => { |
| 39 | // The state of the asynchronous callback. |
| 40 | const [ asyncState, setAsyncState ] = useState<AsyncCallbackState<R>>( { |
| 41 | status: 'idle' |
| 42 | } ); |
| 43 | |
| 44 | // A reference to the mounted state of the component. |
| 45 | const unmountedRef = useIsUnmountedRef(); |
| 46 | |
| 47 | // A reference to the previous execution UUID. It is used to prevent race conditions between multiple executions |
| 48 | // of the asynchronous function. If the UUID of the current execution is different than the UUID of the previous |
| 49 | // execution, the state is not updated. |
| 50 | const prevExecutionUIDRef = useRef<string | null>( null ); |
| 51 | |
| 52 | // The asynchronous executor function, which is a wrapped version of the original callback. |
| 53 | const asyncExecutor = useRefSafeCallback( async ( ...args: A ) => { |
| 54 | if ( unmountedRef.current || isSSR() ) { |
| 55 | return null; |
| 56 | } |
| 57 | |
| 58 | const currentExecutionUUID = uid(); |
| 59 | prevExecutionUIDRef.current = currentExecutionUUID; |
| 60 | |
| 61 | try { |
| 62 | // Prevent unnecessary state updates, keep loading state if the status is already 'loading'. |
| 63 | if ( asyncState.status !== 'loading' ) { |
| 64 | setAsyncState( { |
| 65 | status: 'loading' |
| 66 | } ); |
| 67 | } |
| 68 | |
| 69 | // Execute the asynchronous function. |
| 70 | const result = await callback( ...args ); |
| 71 | |
| 72 | // Update the state if the component is still mounted and the execution UUID matches the previous one, otherwise |
| 73 | // ignore the result and keep the previous state. |
| 74 | if ( !unmountedRef.current && prevExecutionUIDRef.current === currentExecutionUUID ) { |
| 75 | setAsyncState( { |
| 76 | status: 'success', |
| 77 | data: result |
| 78 | } ); |
| 79 | } |
| 80 | |
| 81 | return result; |
| 82 | } catch ( error: any ) { |
| 83 | console.error( error ); |
| 84 | |
| 85 | // Update the state if the component is still mounted and the execution UUID matches the previous one, otherwise |
| 86 | if ( !unmountedRef.current && prevExecutionUIDRef.current === currentExecutionUUID ) { |
| 87 | setAsyncState( { |
| 88 | status: 'error', |
| 89 | error |
| 90 | } ); |
| 91 | } |
| 92 | } |
| 93 |
no test coverage detected
searching dependent graphs…