Skip to content

Instantly share code, notes, and snippets.

@antoinerousseau
Last active January 3, 2022 12:19
Show Gist options
  • Save antoinerousseau/0debb5aa251c3abe4705e9ac50a45089 to your computer and use it in GitHub Desktop.
Save antoinerousseau/0debb5aa251c3abe4705e9ac50a45089 to your computer and use it in GitHub Desktop.
/**
* This is an example React hook I wrote to demonstrate the use of an AbortController
* to avoid setting state on unmounted components
*
* Example use:
* const { data, loading, error } = useFetch<MyDataType>("https://reqres.in/api/users")
*/
import { useState, useEffect } from "react"
import HttpError from "standard-http-error"
interface Options {
method?: string
params?: Record<string, any>
// could add more custom options used for RequestInit
}
export const useFetch = <T>(uri: string, options: Options = {}) => {
const [data, setData] = useState<T>()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error>()
useEffect(() => {
// https://developer.mozilla.org/en-US/docs/Web/API/AbortController
const abortController =
typeof AbortController !== "undefined" ? new AbortController() : undefined
setLoading(true)
// reset for when dependencies changed and this effect is triggered again:
setData(undefined)
setError(undefined)
const init: RequestInit = {
method: options.method, // undefined == GET
signal: abortController && abortController.signal,
}
const headers = new Headers()
headers.set("Accept", "application/json")
if (options.params) {
if (options.params instanceof FormData) {
init.body = options.params
} else {
headers.set("Content-Type", "application/json")
init.body = JSON.stringify(options.params)
}
// could also convert params to query string and append it to uri if method is GET
}
init.headers = headers
fetch(uri, init)
.then((response) => {
setLoading(false)
if (response.status >= 400) {
// could also be limited to >= 500 to decode 400 response bodies
throw new HttpError(response.status, response.statusText)
}
return response.json()
})
.then(setData)
.catch((error) => {
if (error.name === "AbortError") {
// do not perform state updates because the abort means the calling component got unmounted
return
}
setLoading(false)
setError(error)
})
if (abortController) {
return () => {
abortController.abort()
}
}
}, [uri, options.method, options.params]) // you could also do a shallow comparison of the params
return { data, loading, error }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment