( asyncCallback: AsyncCallback<TArgs, TData>, onStateChange: (state: AsyncState<TData, TError>) => void )
| 32 | export type AsyncCallback<TArgs extends unknown[], TData> = (...args: TArgs) => Promise<TData>; |
| 33 | |
| 34 | export const useAsync = <TData, TError, TArgs extends unknown[]>( |
| 35 | asyncCallback: AsyncCallback<TArgs, TData>, |
| 36 | onStateChange: (state: AsyncState<TData, TError>) => void |
| 37 | ): AsyncCallback<TArgs, TData> => { |
| 38 | const alive = useAlive(); |
| 39 | |
| 40 | // Tracks the request number. |
| 41 | // If two or more requests are made subsequently |
| 42 | // we will throw all old request's response after they resolved. |
| 43 | const reqNumberRef = useRef(0); |
| 44 | |
| 45 | const callback: AsyncCallback<TArgs, TData> = useCallback( |
| 46 | async (...args) => { |
| 47 | queueMicrotask(() => { |
| 48 | // Warning: flushSync was called from inside a lifecycle method. |
| 49 | // React cannot flush when React is already rendering. |
| 50 | // Consider moving this call to a scheduler task or micro task. |
| 51 | flushSync(() => { |
| 52 | // flushSync because |
| 53 | // https://github.com/facebook/react/issues/26713#issuecomment-1872085134 |
| 54 | onStateChange({ |
| 55 | status: AsyncStatus.Loading, |
| 56 | }); |
| 57 | }); |
| 58 | }); |
| 59 | |
| 60 | reqNumberRef.current += 1; |
| 61 | |
| 62 | const currentReqNumber = reqNumberRef.current; |
| 63 | try { |
| 64 | const data = await asyncCallback(...args); |
| 65 | if (currentReqNumber !== reqNumberRef.current) { |
| 66 | throw new Error('AsyncCallbackHook: Request replaced!'); |
| 67 | } |
| 68 | if (alive()) { |
| 69 | queueMicrotask(() => { |
| 70 | onStateChange({ |
| 71 | status: AsyncStatus.Success, |
| 72 | data, |
| 73 | }); |
| 74 | }); |
| 75 | } |
| 76 | return data; |
| 77 | } catch (e) { |
| 78 | if (currentReqNumber !== reqNumberRef.current) { |
| 79 | throw new Error('AsyncCallbackHook: Request replaced!'); |
| 80 | } |
| 81 | |
| 82 | if (alive()) { |
| 83 | queueMicrotask(() => { |
| 84 | onStateChange({ |
| 85 | status: AsyncStatus.Error, |
| 86 | error: e as TError, |
| 87 | }); |
| 88 | }); |
| 89 | } |
| 90 | throw e; |
| 91 | } |
no test coverage detected