import { useCallback, useState } from 'react' /** * Configuration options for an action. * It can be used to configure the behavior of the useAction hook. */ export type ActionConfig = { /** Callback function to be called when an error occurs while executing the action. */ onError?: (err: Error) => void /** Callback function to be called when the action is successful. */ onSuccess?: (data: ReturnOf) => void } type MultipleParams = T extends unknown[] ? T : [T] type ActionFn = T extends (...args: infer Args) => Promise ? (...args: Args) => Promise : never type ParamsOf = Parameters> type ReturnOf = Awaited>> /** * Hook to create an action from a namespace. * @internal * @param namespace - The namespace to create the action from. * @param fnName - The name of the action to create. Example: `Namespace.fnName` * @param config - Configuration options for the action. * @returns The action and its loading state. */ export const useAction = ( namespace: Namespace | undefined, fnName: Key & string, config?: ActionConfig, ) => { const [status, setStatus] = useState<'loading' | 'error' | 'success' | 'idle'>('idle') const [error, setError] = useState() const [data, setData] = useState | undefined>() const action = useCallback( async (...args: MultipleParams>): Promise> => { if (!namespace) { throw new Error(`useAction: namespace is undefined`) } const fn = namespace[fnName] as ActionFn if (typeof fn !== 'function') { throw new Error(`useAction: fn ${fnName} is not a function`) } setStatus('loading') try { const data = (await fn.apply(namespace, args)) as ReturnOf setData(data) setStatus('success') config?.onSuccess?.(data) return data as ReturnOf } catch (error: unknown) { setStatus('error') if (error instanceof Error) { setError(error) config?.onError?.(error) } // Let the caller handle the error throw error } finally { setStatus('idle') } }, [config, fnName, namespace], ) return { /** The action to execute. */ action, /** The data returned by the action. */ data, /** The error that occurred while executing the action. */ error, /** Whether the action is pending. */ isPending: status === 'loading', /** Whether the action is successful. */ isSuccess: status === 'success', /** Whether the action is in error. */ isError: status === 'error', } }