import { useCallback, useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { getToken } from '../utils/auth' import { AbortError, handleHTTPErrors, isAborted } from '../utils' function useSend>( url: string, method: RequestInit['method'], needAuth: boolean, guardResponse: (data: unknown) => data is R, processResponse: (data: R) => T, defaultParams?: Omit, ) { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const navigate = useNavigate() const abortControllerRef = useRef() useEffect(() => () => { const reason = new AbortError('unmount') abortControllerRef.current?.abort(reason) }, []) /** Don't use in useEffect. If you need request result, go with useFetch instead */ const doSend = useCallback(async (urlProps?: Record, params?: Omit) => { setLoading(true) setError(null) if (abortControllerRef.current) { const reason = new AbortError('resent') abortControllerRef.current.abort(reason) } 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 null } headers.append('Authorization', `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) { if (isAborted(err)) { if (err.message !== 'resent') { setLoading(false) } if (err.fallback !== undefined) { return err.fallback } return undefined } else { if (err instanceof TypeError) { setError('Ошибка сети') } else { setError(err.message) } if (import.meta.env.DEV) { console.error(url, params, err) } setLoading(false) } } return null } }, [defaultParams, needAuth, navigate, url, method, guardResponse, processResponse]) return { doSend, loading, error, abort: abortControllerRef.current?.abort.bind(abortControllerRef.current), } } export default useSend