Skip to content

Instantly share code, notes, and snippets.

@reverofevil
Last active May 16, 2023 23:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reverofevil/169f7d97eedbb82d1eb5f14aaf3ad6f9 to your computer and use it in GitHub Desktop.
Save reverofevil/169f7d97eedbb82d1eb5f14aaf3ad6f9 to your computer and use it in GitHub Desktop.
Hook for fetch
import React, { useState, useEffect, useReducer, FC } from 'react';
type FetchState =
| { readonly type: 'initial' }
| { readonly type: 'loading', readonly onAbort: () => void }
| { readonly type: 'error', readonly error: unknown, readonly onRetry: () => void }
| { readonly type: 'ok', readonly value: unknown }
| { readonly type: 'aborted', readonly onRetry: () => void }
const initial: FetchState = { type: 'initial' };
type FetchArgs = Parameters<typeof fetch>
const useFetch = (...args: FetchArgs): FetchState => {
const [input, init] = args;
const [data, setData] = useState<FetchState>(initial);
const [retry, onRetry] = useReducer(s => 1 + s, 0);
useEffect(() => {
const { abort, signal } = new AbortController();
const onAbort = () => {
setData({ type: 'aborted', onRetry });
abort();
};
setData({ type: 'loading', onAbort });
(async () => {
try {
setData({
type: 'ok',
value: await (await fetch(input, {
...(init || {}),
signal,
})).json(),
});
} catch (error) {
if (!signal.aborted) {
setData({ type: 'error', error, onRetry });
}
}
})();
return onAbort;
}, [input, init, retry]);
useEffect(() => {
if (!init || !init.signal) {
return;
}
const signal = init.signal;
const listener = () => {
if (data.type === 'loading') {
data.onAbort();
}
};
signal.addEventListener('abort', listener);
return () => signal.removeEventListener('abort', listener);
}, [init?.signal]);
return data;
};
const Component: FC<{ url: string }> = ({ url }) => {
const data = useFetch(url);
switch (data.type) {
case 'initial': return <div>Initial</div>;
case 'loading': return <button onClick={data.onAbort}>Loading</button>;
case 'error': return <pre>Error: {String(data.error)} <button onClick={data.onRetry}>Retry</button></pre>;
case 'aborted': return <div>Aborted <button onClick={data.onRetry}>Retry</button></div>;
case 'ok': return <div>{JSON.stringify(data.value, null, 4)}</div>;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment