| import { useState, useEffect } from 'react'; | |
| import api from '../services/api-wrapper.ts'; | |
| import type { ApiError } from '../types/api.types'; | |
| type UseApiState<T> = { | |
| data: T | null; | |
| loading: boolean; | |
| error: ApiError | null; | |
| }; | |
| type UseApiOptions = { | |
| immediate?: boolean; // Whether to fetch immediately on mount | |
| }; | |
| /** | |
| * Custom hook for making API calls with loading and error states | |
| * | |
| * @example | |
| * const { data, loading, error, execute } = useApi<User[]>('/api/users'); | |
| * | |
| * // Or with manual trigger | |
| * const { data, loading, error, execute } = useApi<User[]>('/api/users', { immediate: false }); | |
| * execute(); // Call manually | |
| */ | |
| export function useApi<T>( | |
| url: string, | |
| options: UseApiOptions = { immediate: true } | |
| ): UseApiState<T> & { execute: () => Promise<void>; refetch: () => Promise<void> } { | |
| const [state, setState] = useState({ | |
| data: null, | |
| loading: options.immediate ?? true, | |
| error: null, | |
| } as UseApiState<T>); | |
| const execute = async () => { | |
| setState((prev) => ({ ...prev, loading: true, error: null })); | |
| try { | |
| const data = await api.get<T>(url); | |
| setState({ data, loading: false, error: null }); | |
| } catch (err) { | |
| setState({ | |
| data: null, | |
| loading: false, | |
| error: err as ApiError, | |
| }); | |
| } | |
| }; | |
| useEffect(() => { | |
| if (options.immediate !== false) { | |
| execute(); | |
| } | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, [url]); | |
| return { | |
| ...state, | |
| execute, | |
| refetch: execute, | |
| }; | |
| } | |
| export default useApi; | |