Skip to content

Instantly share code, notes, and snippets.

@shinkathe
Created September 27, 2022 16:01
Show Gist options
  • Save shinkathe/3a7204cdd88253d971189ca87421c005 to your computer and use it in GitHub Desktop.
Save shinkathe/3a7204cdd88253d971189ca87421c005 to your computer and use it in GitHub Desktop.
useClient hook that handles cancellations automatically
import {
DependencyList,
useEffect,
useCallback,
useState,
useMemo,
} from "react";
import * as R from "ramda";
import { attemptP, fork, FutureInstance } from "fluture";
export type UseClientOptions<ApiResult, ApiException> = {
pred?: () => boolean;
onSuccessEffect?: (arg0: ApiResult) => void;
onFailureEffect?: (arg0: ApiException | Error) => void;
};
/*
A useFetch implementation which takes care of:
- Promise cancellations (fetch will get cancelled all the way to browser side)
- Loading states
- Error outcomes
- Declarative api
- Manual triggering api
What's missing:
- Synchronous API (would be available in future chain but is not exposed)
*/
export const useFetch = <ApiResult, ApiException>(
apiPromise: () => Promise<ApiResult>,
deps: DependencyList,
options = {} as UseClientOptions<ApiResult, ApiException>
) => {
const [apiResultState, setResult] = useState<ApiResult | undefined>();
const [loading, setLoading] = useState<boolean>(false);
const [immediate, setImmediate] = useState<Date | null>(null);
// Deps will include any parameters inside the predicate
// eslint-disable-next-line
const predMemoed = useCallback(options.pred ?? R.T, deps);
// Deps will include any parameters inside the promise
// eslint-disable-next-line
const apiPromiseMemoed = useCallback(apiPromise, deps);
const { onSuccessEffect, onFailureEffect } = options;
const onError = useCallback(
(error: ApiException | Error) => {
setLoading(false);
// we want to see errors in console during development time
// eslint-disable-next-line
if (process.env.NODE_ENV === "development") console.error(error);
onFailureEffect?.(error);
},
[onFailureEffect]
);
const onSuccess = useCallback(
(result: ApiResult) => {
setResult(result);
setLoading(false);
onSuccessEffect?.(result);
},
[onSuccessEffect]
);
const attempt: FutureInstance<Error | ApiException, ApiResult> = useMemo(
() => attemptP(apiPromiseMemoed),
[apiPromiseMemoed]
);
useEffect(() => {
if (!predMemoed()) return;
setLoading(true);
return fork(onError)(onSuccess)(attempt);
// Deps will include any parameters inside the promise
// eslint-disable-next-line
}, [...deps, immediate, onError, onSuccess, attempt, predMemoed]);
const execute = useCallback(() => {
setImmediate(new Date());
}, []);
return {
loading,
result: apiResultState,
execute,
};
};
@shinkathe
Copy link
Author

Note that it requires ramda and fluture

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment