Skip to content

Instantly share code, notes, and snippets.

@patrixr
Created January 29, 2021 04:12
Show Gist options
  • Save patrixr/112d2e36e6191b72087feeffbfcdf776 to your computer and use it in GitHub Desktop.
Save patrixr/112d2e36e6191b72087feeffbfcdf776 to your computer and use it in GitHub Desktop.
import React, { useState } from "react";
// -------------------------
// Typing
// -------------------------
type Maybe<T> = T | null;
type AnyFunction = (...args: any[]) => any;
type ArgumentsOf<F extends AnyFunction> = F extends (...args: infer A) => any
? A
: never;
type Awaited<T> = T extends { then(onfulfilled: (value: infer U) => any): any }
? U
: T extends { then(...args: any[]): any }
? never
: T;
type MethodWithHook<MethodType extends AnyFunction> = MethodType & {
use: (
defaultVal: Awaited<ReturnType<MethodType>>
) => [
// Trigger
(...args: ArgumentsOf<MethodType>) => void,
// Data
Awaited<ReturnType<MethodType>>,
// Loading
boolean,
// Error
Maybe<Error>
];
};
interface GenericService {
[key: string]: AnyFunction;
}
type MappedService<T extends GenericService> = {
[K in keyof T]: MethodWithHook<T[K]>;
};
// -------------------------
// Hook Generator
// -------------------------
const methodFactory = <T extends AnyFunction>(method: T): MethodWithHook<T> => {
const methodWrapper = (...args: any[]) => method(...args);
const useServiceMethod = (defaultVal: Awaited<ReturnType<T>>) => {
const [error, setError] = useState<Maybe<Error>>(null);
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState<Awaited<ReturnType<T>>>(defaultVal);
const trigger: any = async (...args: any[]) => {
try {
setLoading(true);
setError(null);
setData(await method(...args));
} catch (e) {
setError(new Error(e));
} finally {
setLoading(false);
}
};
return [trigger, data, loading, error];
};
methodWrapper.use = useServiceMethod;
return methodWrapper as MethodWithHook<T>;
};
// -------------------------
// Entrypoint
// -------------------------
/**
*
* @param {T} service A service you wish to add hook support to
* @returns {MappedService<T>} A clone of service the service, with each method having a 'use' submethod
*/
export const usableService = <T extends GenericService>(
service: T
): MappedService<T> => {
return Object.keys(service).reduce((hook, key) => {
if (typeof service[key] === "function") {
hook[key] = methodFactory(service[key]);
}
return hook;
}, {} as any) as MappedService<T>;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment