Files
schulos/src/lib/hooks/useApi.ts
T
2026-08-02 12:33:19 +02:00

199 lines
4.9 KiB
TypeScript

'use client';
import { useCallback, useState, useEffect } from 'react';
import useSWR, { SWRConfiguration } from 'swr';
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
status: number;
}
export interface UseApiOptions extends SWRConfiguration {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
body?: any;
headers?: HeadersInit;
onSuccess?: (data: any) => void;
onError?: (error: any) => void;
skip?: boolean;
}
const apiUrl = process.env.NEXT_PUBLIC_API_URL || '';
/**
* Standardized API fetcher with error handling
*/
export const apiFetcher = async (
url: string,
options?: {
method?: string;
body?: any;
headers?: HeadersInit;
}
): Promise<any> => {
const method = options?.method || 'GET';
const headers = {
'Content-Type': 'application/json',
...options?.headers,
};
const response = await fetch(`${apiUrl}${url}`, {
method,
headers,
body: options?.body ? JSON.stringify(options.body) : undefined,
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.error || `HTTP ${response.status}`);
}
return response.json();
};
/**
* Hook for fetching data with built-in caching (GET requests)
* @param url - API endpoint
* @param options - SWR options
* @returns { data, error, isLoading, mutate }
*/
export function useApiGet<T = any>(
url: string | null,
options?: SWRConfiguration & { skip?: boolean }
) {
const { skip = false, ...swrOptions } = options || {};
const { data, error, isLoading, mutate } = useSWR<T>(
skip || !url ? null : url,
(url) => apiFetcher(url),
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 60000,
...swrOptions,
}
);
return {
data,
error,
isLoading: isLoading && !error,
mutate,
};
}
/**
* Hook for mutations (POST, PUT, DELETE, PATCH)
* @param url - API endpoint
* @param options - Custom options including onSuccess, onError callbacks
* @returns { mutate, isLoading, error, data }
*/
export function useApiMutation<T = any>(
url: string,
options?: UseApiOptions
) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<T | null>(null);
const mutate = useCallback(
async (payload?: any) => {
setIsLoading(true);
setError(null);
try {
const response = await fetch(`${apiUrl}${url}`, {
method: options?.method || 'POST',
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
body: JSON.stringify(payload || options?.body),
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error || `HTTP ${response.status}`);
}
setData(result.data || result);
options?.onSuccess?.(result.data || result);
return result.data || result;
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error';
setError(message);
options?.onError?.(err);
throw err;
} finally {
setIsLoading(false);
}
},
[url, options]
);
return { mutate, isLoading, error, data };
}
/**
* Hook for complex queries with multiple dependencies
* Useful for parallel requests
*/
export function useApiQueries<T = any>(
queries: Array<{ url: string | null; key?: string }>,
options?: SWRConfiguration
) {
const activeQueries = queries.filter((query) => query.url);
const cacheKey = activeQueries.map((query) => query.url).join("|") || null;
const { data: values, error, isLoading } = useSWR<T[]>(
cacheKey,
() => Promise.all(activeQueries.map((query) => apiFetcher(query.url!))),
options
);
const data = Object.fromEntries(
activeQueries.map((query, index) => [query.key || query.url, values?.[index]])
) as Record<string, T | undefined>;
return { data, error, isLoading };
}
/**
* Imperative API call (for one-off requests)
*/
export const apiCall = async <T = any>(
url: string,
options?: {
method?: string;
body?: any;
headers?: HeadersInit;
}
): Promise<T> => {
return apiFetcher(`${apiUrl}${url}`, options);
};
/**
* Convenience methods
*/
export const apiGet = async <T = any>(url: string): Promise<T> => {
return apiCall(url, { method: 'GET' });
};
export const apiPost = async <T = any>(url: string, body?: any): Promise<T> => {
return apiCall(url, { method: 'POST', body });
};
export const apiPut = async <T = any>(url: string, body?: any): Promise<T> => {
return apiCall(url, { method: 'PUT', body });
};
export const apiDelete = async <T = any>(url: string, body?: any): Promise<T> => {
return apiCall(url, { method: 'DELETE', body });
};
export const apiPatch = async <T = any>(url: string, body?: any): Promise<T> => {
return apiCall(url, { method: 'PATCH', body });
};