A general-purpose fetch hook. This forms the foundation of the Standard Resource fetch hook.
import useFetch from './use-fetch'; | |
export default function book({ bookId }) { | |
const { | |
// wow so much stuff | |
fetching, | |
failed, | |
succeeded, | |
data, | |
res, | |
error, | |
abortController, | |
abortSignal, | |
// Call this to make the request yourself | |
request | |
} = useFetch( | |
// This is a function that actually makes the network request. | |
// Note that `fetchBooks` **must** always resolve! | |
() => fetchBooks(bookId), | |
// This does two things: it makes the fetcher automatically get called on mount, | |
// and also get called _again_ anytime that this value changes. | |
[bookId], | |
{ | |
// When the 2nd argument is passed, you can prevent fetches by passing false as this | |
// value. In this situation, we won't fetch the books when the ID isn't a valid number | |
okayToFetch: Number.isNaN(bookId), | |
// These are probably dumb options that likely don't need to exist | |
abortPrevious, | |
abortOnUmount | |
} | |
); | |
// Because there is no second arg, this one is not called on mount (nor will it ever be | |
// called automatically for you). You must manually call it using `request()` | |
const [request] = useFetch(() => deleteBook(bookId)); | |
} |
import { useState, useEffect, useRef } from 'react'; | |
const defaultFetchStatus = { | |
data: null, | |
res: null, | |
failed: false, | |
succeeded: false, | |
error: null, | |
}; | |
export default function useFetcher( | |
fn, | |
resolveCondition, | |
// TODO: think about this API...`okayToFetch` is going to be much more common | |
// than everything else. | |
// Also, I need to accept an AbortController factory. | |
{ okayToFetch = true, abortPrevious = true, abortOnUnmount = true } = {} | |
) { | |
const automaticallyFetch = Array.isArray(resolveCondition); | |
const [fetchResults, setFetchResults] = useState({ | |
...defaultFetchStatus, | |
fetching: automaticallyFetch, | |
}); | |
const abortControllerRef = useRef(); | |
const request = () => { | |
if (!okayToFetch) { | |
return; | |
} | |
if (abortPrevious) { | |
if ( | |
fetchResults.abortController && | |
typeof fetchResults.abortController.abort === 'function' | |
) { | |
fetchResults.abortController.abort(); | |
} | |
} | |
const abortController = new AbortController(); | |
abortControllerRef.current = abortController; | |
let isPreviousRequest = false; | |
setFetchResults({ | |
...fetchResults, | |
data: null, | |
fetching: true, | |
abortController, | |
abortSignal: abortController.signal, | |
}); | |
const options = { | |
signal: abortController.signal, | |
}; | |
fn(options).then(result => { | |
abortControllerRef.current = null; | |
if (isPreviousRequest) { | |
return; | |
} | |
setFetchResults({ | |
data: result.res ? result.res.data : null, | |
res: result.res, | |
fetching: false, | |
failed: !result.success, | |
succeeded: result.success, | |
error: result.err, | |
abortController: null, | |
abortSignal: null, | |
}); | |
}); | |
return () => { | |
isPreviousRequest = true; | |
}; | |
}; | |
if (automaticallyFetch) { | |
useEffect(request, resolveCondition); | |
} | |
// This code supports aborting the request when the component unmounts | |
useEffect(() => { | |
return () => { | |
if (abortOnUnmount) { | |
if ( | |
abortControllerRef.current && | |
typeof abortControllerRef.current.abort === 'function' | |
) { | |
abortControllerRef.current.abort(); | |
} | |
} | |
}; | |
}, []); | |
return { | |
...fetchResults, | |
request, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment