101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
|
|
import { getToken } from '../utils/auth'
|
|
import { handleHTTPErrors, isAborted } from '../utils'
|
|
|
|
function useSend<R, T extends NonNullable<unknown>>(
|
|
url: string,
|
|
method: RequestInit['method'],
|
|
needAuth: boolean,
|
|
guardResponse: (data: unknown) => data is R,
|
|
processResponse: (data: R) => T,
|
|
startWithLoading = false,
|
|
defaultParams?: Omit<RequestInit, 'method'>
|
|
) {
|
|
const [loading, setLoading] = useState(startWithLoading)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const navigate = useNavigate()
|
|
|
|
const abortControllerRef = useRef<AbortController>()
|
|
|
|
useEffect(() => () => abortControllerRef.current?.abort(), [])
|
|
|
|
/** Don't use in useEffect. If you need request result, go with useFetch instead */
|
|
const doSend = useCallback(async (urlProps?: Record<string, string>, params?: Omit<RequestInit, 'method'>) => {
|
|
setLoading(true)
|
|
setError(null)
|
|
|
|
if (abortControllerRef.current) {
|
|
abortControllerRef.current.abort()
|
|
}
|
|
|
|
const abortController = new AbortController()
|
|
abortControllerRef.current = abortController
|
|
|
|
const headers = new Headers({
|
|
...defaultParams?.headers,
|
|
...params?.headers
|
|
})
|
|
|
|
if (needAuth) {
|
|
const token = getToken()
|
|
|
|
if (token === null) {
|
|
navigate('/login')
|
|
|
|
return undefined
|
|
}
|
|
|
|
headers.append('Auth', `Bearer ${token}`)
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(url + new URLSearchParams(urlProps).toString(), {
|
|
method,
|
|
...defaultParams,
|
|
...params,
|
|
headers,
|
|
signal: abortControllerRef.current.signal,
|
|
})
|
|
|
|
handleHTTPErrors(res)
|
|
|
|
const data: unknown = await res.json()
|
|
|
|
if (!guardResponse(data)) {
|
|
throw new Error('Malformed server response')
|
|
}
|
|
|
|
setLoading(false)
|
|
|
|
return processResponse(data)
|
|
|
|
} catch (err) {
|
|
if (err instanceof Error && !isAborted(err)) {
|
|
if (err instanceof TypeError) {
|
|
setError('Ошибка сети')
|
|
} else {
|
|
setError(err.message)
|
|
}
|
|
|
|
if (import.meta.env.DEV) {
|
|
console.log(url, params, err)
|
|
}
|
|
}
|
|
|
|
setLoading(false)
|
|
|
|
return undefined
|
|
}
|
|
}, [defaultParams, needAuth, navigate, url, method, guardResponse, processResponse])
|
|
|
|
return {
|
|
doSend, loading, error,
|
|
abort: abortControllerRef.current?.abort.bind(abortControllerRef.current)
|
|
}
|
|
}
|
|
|
|
export default useSend
|