create base components

This commit is contained in:
Timofey
2025-08-29 14:01:39 +03:00
parent 691315da0d
commit ffe77d1ce0
15 changed files with 1577 additions and 160 deletions

View File

@@ -0,0 +1,131 @@
import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query'
import axios, { AxiosError, AxiosRequestConfig } from 'axios'
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
interface FetchOptions<TData, TVariables, TError> {
method?: HttpMethod
config?: AxiosRequestConfig
queryOptions?: Omit<UseQueryOptions<TData, TError>, 'queryKey' | 'queryFn'>
mutationOptions?: Omit<UseMutationOptions<TData, TError, TVariables>, 'mutationFn'>
}
interface QueryResult<TData, TError> {
data: TData | undefined
isLoading: boolean
error: TError | null
refetch: () => Promise<unknown>
}
interface MutationResult<TData, TVariables, TError> {
mutate: (variables: TVariables) => void
isLoading: boolean
error: TError | null
data: TData | undefined
}
export function useClientFetch<TData = unknown, TVariables = void, TError = AxiosError>(
url: string,
options: FetchOptions<TData, TVariables, TError> = {}
): TVariables extends void
? QueryResult<TData, TError>
: MutationResult<TData, TVariables, TError> {
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<TData, TError>({
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<TData, TError, TVariables>({
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<TData, TError> : never
}
return {
mutate: mutation.mutate,
isLoading: mutation.isPending,
error: mutation.error,
data: mutation.data,
} as TVariables extends void ? never : MutationResult<TData, TVariables, TError>
}
// примеры использования:
/*
// GET запрос
interface UserData {
id: number
name: string
email: string
}
const { data, isLoading, error } = useClientFetch<UserData>('/users/me')
// POST запрос с типизированным payload
interface LoginPayload {
email: string
password: string
}
interface LoginResponse {
token: string
user: UserData
}
const { mutate, isLoading } = useClientFetch<LoginResponse, LoginPayload>('/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<UserData, UpdateUserPayload>('/users/me', {
method: 'PATCH'
})
// использование:
mutate({ name: 'New Name' })
*/