Created
August 27, 2023 14:49
-
-
Save jgdev/fd24fd5cd2c72b065d846fc116d4ec8d to your computer and use it in GitHub Desktop.
React Hook to fetch API resources
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useEffect, useState } from "react"; | |
type Status = "idle" | "loading" | "error" | "fetched" | "debouncing"; | |
export type Options = { | |
timeout?: number; | |
debounceTime?: number; | |
}; | |
export type FetchAttempt<T> = Partial<RequestInit> & { | |
lastFetched: number; | |
data?: T | any; | |
queryString?: T | any; | |
}; | |
type Return<T, K> = { | |
error: { detail: any } | null | undefined; | |
data: K | null | undefined; | |
status: Status; | |
post: (params?: Partial<FetchAttempt<T>>) => void; | |
patch: (params?: Partial<FetchAttempt<T>>) => void; | |
del: (params?: Partial<FetchAttempt<T>>) => void; | |
get: (params?: Partial<FetchAttempt<T>>) => void; | |
loading: boolean; | |
}; | |
export function useFetch<T = unknown, K = unknown>( | |
url?: string, | |
options?: Options | |
): Return<T, K> { | |
const [status, setStatus] = useState<Status>("idle"); | |
const [fetchAttempt, _setFetchAttempt] = useState< | |
FetchAttempt<T> | undefined | |
>(); | |
const [data, setData] = useState<K | null | undefined>(null); | |
const [error, setError] = useState< | |
{ detail: any; error: any } | null | undefined | |
>(null); | |
const [loading, setLoading] = useState(false); | |
const setFetchAttempt = (params: FetchAttempt<T>) => { | |
setError(null); | |
setData(null); | |
_setFetchAttempt(params); | |
}; | |
useEffect(() => { | |
setLoading(status === "loading"); | |
}, [status, setLoading]); | |
useEffect(() => { | |
if (!fetchAttempt || !url) return; | |
setStatus("debouncing"); | |
const timeout = setTimeout(() => { | |
const fetchData = async () => { | |
const { method, data, queryString } = fetchAttempt; | |
let _url = url; | |
if (queryString) { | |
_url = `${_url}?${new URLSearchParams(queryString).toString()}`; | |
} | |
setStatus("loading"); | |
const options: RequestInit = {}; | |
if (data) { | |
options.body = JSON.stringify(data); | |
options.headers = { | |
...(options.headers || {}), | |
"Content-Type": "application/json", | |
}; | |
} | |
try { | |
const res = await fetch(_url, { | |
method, | |
...options, | |
}); | |
const body = await res.json(); | |
if (!res.ok) { | |
setError(body); | |
setStatus("error"); | |
return; | |
} | |
setStatus("fetched"); | |
setData(body); | |
} catch (err) { | |
setStatus("error"); | |
setError({ detail: "Failed to fetch", error: err }); | |
} finally { | |
setLoading(false); | |
} | |
}; | |
fetchData(); | |
}, options?.debounceTime || 0); | |
return () => { | |
clearTimeout(timeout); | |
}; | |
}, [fetchAttempt, url, setLoading, options?.debounceTime]); | |
return { | |
status, | |
error, | |
data, | |
loading, | |
post: (params?: Partial<FetchAttempt<T>>) => { | |
setFetchAttempt({ | |
...params, | |
method: "POST", | |
lastFetched: Date.now(), | |
}); | |
}, | |
patch: () => {}, | |
del: () => {}, | |
get: (params?: Partial<FetchAttempt<T>>) => { | |
setFetchAttempt({ | |
...params, | |
data: undefined, | |
method: "GET", | |
lastFetched: Date.now(), | |
}); | |
}, | |
}; | |
} | |
export const useApi = <T = unknown, K = unknown>( | |
method: string, | |
options?: Options | |
): Return<T, K> => { | |
const url = import.meta.env.VITE_API_BASE_URL; | |
if (!url) throw new Error("Api base url not defined"); | |
return useFetch(url + method, options); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment