porridger/front/src/hooks/api/useFetch.ts
dm1sh a8b7cfbffa
Added TypeScript for frontend
Added type definitions for components, functions, data
Added guards for network responses
fixes #8
2023-07-12 19:03:24 +03:00

65 lines
2.1 KiB
TypeScript

import { useEffect, useRef, useState } from "react"
import { isAborted } from '../../utils'
const useFetch = <T>(url: string, params: RequestInit | undefined, initialData: T, dataGuard: (obj: unknown) => obj is T) => {
const [data, setData] = useState(initialData)
const [loading, setLoading] = useState(true)
const [error, setError] = useState("")
const abortControllerRef = useRef<AbortController>()
useEffect(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort()
}
const abortController = new AbortController()
abortControllerRef.current = abortController
fetch(url, { ...params, signal: abortControllerRef.current.signal })
.then(res => {
if (!res.ok) {
switch (res.status) {
case 401:
throw new Error("Ошибка авторизации")
case 404:
throw new Error("Объект не найден")
default: {
throw new Error("Ошибка ответа от сервера")
}
}
}
return res.json()
})
.then(data => {
if (!dataGuard(data)) {
throw new Error("Неверный ответ от сервера")
}
setData(data)
setLoading(false)
})
.catch(err => {
if (err instanceof Error && !isAborted(err)) {
setError("Ошибка сети")
}
setLoading(false)
if (import.meta.env.DEV) {
console.log(url, params, err)
}
})
return () => abortControllerRef.current?.abort()
}, [url, params, dataGuard])
return {
data, loading, error,
abort: abortControllerRef.current?.abort.bind(abortControllerRef.current)
}
}
export default useFetch