import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query' import axios, { AxiosError, AxiosRequestConfig } from 'axios' type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' interface FetchOptions { method?: HttpMethod config?: AxiosRequestConfig queryOptions?: Omit, 'queryKey' | 'queryFn'> mutationOptions?: Omit, 'mutationFn'> } interface QueryResult { data: TData | undefined isLoading: boolean error: TError | null refetch: () => Promise } interface MutationResult { mutate: (variables: TVariables) => void isLoading: boolean error: TError | null data: TData | undefined } export function useClientFetch( url: string, options: FetchOptions = {} ): TVariables extends void ? QueryResult : MutationResult { const { method = 'GET', config = {}, queryOptions = {}, mutationOptions = {} } = options const API_URL = process.env.NEXT_PUBLIC_API_URL const fullUrl = url.startsWith('http') ? url : `${API_URL}${url}` // всегда вызываем оба хука const query = useQuery({ queryKey: [url, config.params], queryFn: async () => { const response = await axios.get(fullUrl, config) return response.data }, ...queryOptions, // отключаем автоматическое выполнение для мутаций enabled: method === 'GET' && queryOptions.enabled !== false, }) const mutation = useMutation({ mutationFn: async (variables: TVariables) => { const response = await axios({ method: method.toLowerCase(), url: fullUrl, data: variables, ...config, }) return response.data }, ...mutationOptions, }) // возвращаем соответствующий результат в зависимости от метода if (method === 'GET') { return { data: query.data, isLoading: query.isLoading, error: query.error, refetch: query.refetch, } as TVariables extends void ? QueryResult : never } return { mutate: mutation.mutate, isLoading: mutation.isPending, error: mutation.error, data: mutation.data, } as TVariables extends void ? never : MutationResult } // примеры использования: /* // GET запрос interface UserData { id: number name: string email: string } const { data, isLoading, error } = useClientFetch('/users/me') // POST запрос с типизированным payload interface LoginPayload { email: string password: string } interface LoginResponse { token: string user: UserData } const { mutate, isLoading } = useClientFetch('/auth/login', { method: 'POST', mutationOptions: { onSuccess: (data) => { // data типизирован как LoginResponse }, onError: (error) => { // error типизирован как AxiosError } } }) // использование: mutate({ email: 'user@example.com', password: '123456' }) // PATCH запрос interface UpdateUserPayload { name?: string email?: string } const { mutate } = useClientFetch('/users/me', { method: 'PATCH' }) // использование: mutate({ name: 'New Name' }) */