Saber2pr's Blog

useAsync

import { useEffect, useState } from 'react'

export type UseAsyncOps<T> = {
  manual: boolean
  onSuccess?(result?: T): any
  onError?(err?: Error): any
}

export type UseAsyncResult<A extends any[], T> = {
  data: T
  loading: boolean
  setData(data: T): void
  run: (...args: A) => Promise<T>
}

export function useAsync<A extends any[], T = any>(
  run: (...args: A) => Promise<T>
): UseAsyncResult<A, T>
export function useAsync<A extends any[], T = any>(
  run: (...args: A) => Promise<T>,
  deps: any[]
): UseAsyncResult<A, T>
export function useAsync<A extends any[], T = any>(
  run: (...args: A) => Promise<T>,
  deps: any[],
  ops: UseAsyncOps<T>
): UseAsyncResult<A, T>

export function useAsync<A extends any[], T = any>(
  run: (...args: A) => Promise<T>,
  deps: any[] = [],
  ops?: UseAsyncOps<T>
): UseAsyncResult<A, T> {
  const [data, setData] = useState<T>()
  const [loading, setLoading] = useState<boolean>()

  const main = async (...args: any) => {
    try {
      setLoading(true)
      const result = await run(...args)
      if (ops?.onSuccess) {
        await ops?.onSuccess(result)
      }
      setData(result)
      return result
    } catch (error) {
      if (ops?.onError) {
        await ops?.onError(error)
      }
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    if (!ops?.manual) {
      main()
    }
  }, [ops?.manual, ...deps])

  return {
    data,
    loading,
    run: main,
    setData,
  }
}