Skip to content

Instantly share code, notes, and snippets.

@saitonakamura
Created December 3, 2019 09:36
Show Gist options
  • Save saitonakamura/53d000df5bf56a2f179f360f5221d1b2 to your computer and use it in GitHub Desktop.
Save saitonakamura/53d000df5bf56a2f179f360f5221d1b2 to your computer and use it in GitHub Desktop.
import { useReducer, Reducer, useRef, useEffect } from "react";
export type RemoteDataState<T> =
| { type: "initial" }
| { type: "loading" }
| { type: "error"; error: Error }
| { type: "complete"; payload: T };
export type RemoteDataAction<T> =
| { type: "beginLoad" }
| { type: "errored"; error: Error }
| { type: "finished"; payload: T };
export function remoteDataReducerFsm<TData>(
state: RemoteDataState<TData>,
action: RemoteDataAction<TData>,
): RemoteDataState<TData> {
switch (action.type) {
case "beginLoad":
switch (state.type) {
case "initial":
case "error":
case "complete":
return { type: "loading" };
default:
return state;
}
case "errored":
switch (state.type) {
case "initial":
case "loading":
return { type: "error", error: action.error };
default:
return state;
}
case "finished":
switch (state.type) {
case "initial":
case "loading":
return { type: "complete", payload: action.payload };
default:
return state;
}
default:
return state;
}
}
export function useRemoteData<TData>(
getData: () => Promise<TData>,
): RemoteDataState<TData> {
const [state, dispatch] = useReducer<
Reducer<RemoteDataState<TData>, RemoteDataAction<TData>>
>(remoteDataReducerFsm, {
type: "initial",
});
const mounted = useRef(true);
useEffect(() => {
if (mounted.current) dispatch({ type: "beginLoad" });
getData()
.then((payload) => {
if (mounted.current) dispatch({ type: "finished", payload });
})
.catch((error) => {
if (mounted.current) {
if (typeof error === "string") {
error = new Error(error);
}
dispatch({ type: "errored", error });
}
});
return () => {
mounted.current = false;
};
}, [getData]);
return state;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment