Created
February 27, 2020 15:18
-
-
Save scorsi/e879b6aa4bf673d3845dfeebcc04b1a1 to your computer and use it in GitHub Desktop.
A Fetch hook with super-powers, lightweight but very performant
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
// eslint-disable-next-line import/no-mutable-exports,prefer-const | |
export let baseUrl = process.env.REACT_APP_API_DOMAIN; | |
export const constructUrl = (path, queryParams = null) => { | |
const _url = new URL(baseUrl); | |
_url.pathname = path; | |
if (queryParams) _url.search = new URLSearchParams(queryParams).toString(); | |
return _url.toString(); | |
}; | |
// eslint-disable-next-line import/no-mutable-exports,prefer-const | |
export let defaultFetchOptions = { | |
method: 'GET', | |
mode: 'cors', | |
credentials: 'include', | |
cache: 'no-cache', | |
}; | |
export const doFetch = (url, options) => fetch(url, { ...defaultFetchOptions, ...options }) | |
.then((response) => { | |
if (!response.ok) { | |
return Promise.reject(response); | |
} | |
return response.json() | |
.then((data) => Promise.resolve({ ...data })); | |
}) | |
.catch((error) => { | |
if (['PromiseCanceledError', 'AbortError'].includes(error.name)) { | |
return Promise.reject(error.name); | |
} | |
// /!\ WARNING DOMAIN-SPECIFIC CODE | |
// My api is always retourning a error name in json | |
// This should be adapted for your use-case | |
if (error instanceof Response) { | |
return error.json() | |
.then((data) => Promise.reject(data.error)); | |
} | |
return Promise.reject('unknown_error'); | |
}); |
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 { | |
useEffect, useMemo, useRef, useState, | |
} from 'react'; | |
import useCancellablePromise from './useCancellablePromise'; | |
import { constructUrl, defaultFetchOptions, doFetch } from './fetch'; | |
export default ( | |
path, | |
options, | |
defaultData = null, | |
) => { | |
const { | |
transformData, queryParams, bodyParams, | |
method, mode, credentials, | |
} = options; | |
const { cancellablePromise } = useCancellablePromise(); | |
const [isLoading, setIsLoading] = useState(true); | |
const [newData, setNewData] = useState(defaultData); | |
const [data, setData] = useState(defaultData); | |
const [error, setError] = useState(null); | |
const controller = useRef(null); | |
const memoizedUrl = useMemo(() => constructUrl(path, queryParams), [path, queryParams]); | |
const memoizedFetchOptions = useMemo(() => ({ | |
method: method || defaultFetchOptions.method, | |
mode: mode || defaultFetchOptions.mode, | |
credentials: credentials || defaultFetchOptions.credentials, | |
body: JSON.stringify(bodyParams) || defaultFetchOptions.body, | |
}), [method, mode, credentials, bodyParams]); | |
useEffect(() => { | |
if (controller.current !== null) controller.current.abort(); | |
controller.current = new AbortController(); | |
setIsLoading(true); | |
cancellablePromise( | |
doFetch(memoizedUrl, { | |
...memoizedFetchOptions, | |
signal: controller.current.signal, | |
}), | |
controller.current, | |
) | |
.then((_data) => { | |
setNewData(_data); | |
}) | |
.catch((_error) => { | |
if ( | |
(_error instanceof String && ['PromiseCanceledError', 'AbortError'].includes(_error)) | |
|| (_error instanceof Object && _error.name === 'PromiseCanceledError') | |
) { | |
return; | |
} | |
setIsLoading(false); | |
setNewData(null); | |
setError(_error); | |
}); | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, [setIsLoading, setNewData, memoizedUrl, memoizedFetchOptions]); | |
useEffect(() => { | |
if (transformData) { | |
setData((oldData) => transformData(newData, oldData)); | |
} else { | |
setData(newData); | |
} | |
setIsLoading(false); | |
}, [newData, setData, transformData]); | |
return { | |
data, | |
error, | |
isLoading, | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment