DEV Community

F.P
F.P

Posted on • Edited on

Generic React Hook for async functions

I came up with the following function to create a React Hook of any async function (if it matches a simple interface with just one input parameter), which turns it into a GraphQL/Apollo-like hook.

type HookableFunc<T, P> = (param: P) => Promise<T|null>; 
type HookedFuncResult<T> = {
    result: T|null;
    err: any;
    pending: boolean;
};

export const asHook = <T, P>(func: HookableFunc<T, P>, param: P): HookedFuncResult<T> => {
    const [result, setResult] = useState<T|null>(null);
    const [err, setErr] = useState<any>();
    const [pending, setPending] = useState<boolean>(false);

    useEffect(() => {
        setPending(true);
        func(param)
            .then(setResult)
            .catch(setErr)
            .finally(() => setPending(false));
    }, [JSON.stringify(param)]);

    return {
        result,
        err,
        pending,
    };
};

Usage:

const myFunc = async(param: string): string[] => {
    // fetch data, use IO, etc.
};

export const useMyFuncHook = (param: string) => asHook(myFunc, param);

// elsewhere
export const MyComponent: React.FC<MyProps> = (props) => {
    const { pending, err, result } = useMyFuncHook(props.param);

    if (pending) return "Loading...";

    if (err) return "Some error occurred: " + JSON.stringify(err);

    if (!result) return "No data found!";

    return <Result data={result} />;
};

For an electron app, I needed to fetch data from the local file system in many different components.

But as I exclusively use Function Components (I find the whole concept of classes in JavaScript a bit weird), and these doesn't play nice together with async functions (which any function that uses external IO of any sort should be naturally anyways), I needed a proper solution to call those functions.

The solution is of course React Effects. Finding and reading this great article, I was happy to have solved my problem.

However the solution proposed there lacks generality and type-safety which I definitely wanted, as I needed this in lots of places and don't want to repeat what boils down to basically the same code over and over again, so I came up with my asHook function.

Top comments (1)

Collapse
 
flopes89 profile image
F.P

While I think this is a neat way to using async functions in React Function Components, there are some problems with it which I cannot fully get rid of (happy for suggestions):

  • I don't like how err can be anything. I realize this is more or less baked into the exception handling of JavaScript, but I'd still love to have more precise typing here
  • If you want to hookify functions that take more than one parameter, you need to create new types for every amount of parameters you want to support, which seems completely ridiculous