Skip to content

Instantly share code, notes, and snippets.

@vercetti11
Created July 29, 2021 14:44
Show Gist options
  • Save vercetti11/ab0eee63211a9876d6b8158d04268abe to your computer and use it in GitHub Desktop.
Save vercetti11/ab0eee63211a9876d6b8158d04268abe to your computer and use it in GitHub Desktop.
safe fetch with loading, error and no race conditions
import { useEffect, useRef, useReducer, useCallback } from 'react';
enum PromiseStatus {
IDLE = 'IDLE',
PENDING = 'PENDING',
RESOLVED = 'RESOLVED',
REJECTED = 'REJECTED',
}
export type State = {
status: PromiseStatus;
loading: boolean;
data: any;
error: string | null;
};
type Action =
| { type: PromiseStatus.PENDING }
| { type: PromiseStatus.RESOLVED; data: any }
| { type: PromiseStatus.REJECTED; error: string };
function asyncReducer(state: State, action: Action): State {
switch (action.type) {
case PromiseStatus.PENDING: {
return { status: PromiseStatus.PENDING, loading: true, data: null, error: null };
}
case PromiseStatus.RESOLVED: {
return {
status: PromiseStatus.RESOLVED,
loading: false,
data: action.data,
error: null,
};
}
case PromiseStatus.REJECTED: {
return {
status: PromiseStatus.REJECTED,
loading: false,
data: null,
error: action.error,
};
}
default: {
throw new Error(`this action is not supported`);
}
}
}
function useFetch(url: string) {
const isCurrent = useRef(false);
const [state, dispatch] = useReducer(asyncReducer, {
status: PromiseStatus.IDLE,
loading: true,
data: null,
error: null,
});
useEffect(() => {
isCurrent.current = true;
return () => {
// called when the component is going to unmount
isCurrent.current = false;
};
}, []);
const fetchURL = useCallback(
(controller) => {
dispatch({ type: PromiseStatus.PENDING });
fetch(url, {
signal: controller.signal,
})
.then((res) => res.json())
.then((data) => {
if (!isCurrent.current) return;
dispatch({ type: PromiseStatus.RESOLVED, data });
})
.catch((error) => {
if (!isCurrent.current) return;
dispatch({ type: PromiseStatus.REJECTED, error });
});
},
[url],
);
useEffect(() => {
const controller = new AbortController();
fetchURL(controller);
return () => controller.abort();
}, [fetchURL]);
return state;
}
export default useFetch;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment