porridger/front/src/hooks/useSend.ts

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