I wrote a custom hook that accepts an async function as parameter.
The custom hook will call the function and return the result when done.
Should I memoize or useCallback when passing the function ?
Here’s a sample usage of my hook
const {
status,
error,
fire,
data
} = useAsyncOperation(
() => {
return Promise.resolve("Async result");
},
{ autofire: true }
);
What I fear is that inline anonymous function will change across renders and that the hook consider it as a new value (even if the function is the same).
How should I handle this ?
FYI: I’ve set up a code sandbox to illustrate the use of my custom hook
And the code of my hook is:
import { useCallback, useEffect, useState } from 'react';
type UseAsyncOperationResult<TResult> = {
status: 'idle' | 'pending' | 'success' | 'error';
fire: () => Promise<TResult>;
error?: Error;
data?: TResult;
};
type UseAsyncOperationOptions<TResult> = {
onSuccess?: (result: TResult) => void;
onError?: (error: Error) => void;
autofire?: boolean;
};
export const useAsyncOperation = <TResult>(
operation: () => Promise<TResult>,
options: UseAsyncOperationOptions<TResult> = {}
): UseAsyncOperationResult<TResult> => {
// TODO: use reducer to manage state in a cleaner way
const [status, setStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle');
const [error, setError] = useState<Error | undefined>();
const [data, setData] = useState<TResult | undefined>();
const [autofired, setAutofired] = useState(false);
const { onSuccess, onError, autofire } = options;
const fire = useCallback(async () => {
setStatus('pending');
try {
const result = await operation();
setStatus('success');
setData(result);
if (onSuccess) onSuccess(result);
return result;
} catch (error) {
setStatus('error');
setError(error as Error);
if (onError) onError(error as Error);
return Promise.reject(error);
}
}, [onError, onSuccess, operation]);
useEffect(() => {
if (autofire && !autofired) {
console.log('autofire');
setAutofired(true);
void fire();
}
}, [autofire, fire, autofired]);
return {
status,
fire,
error,
data,
};
};
>Solution :
Yes, you need to keep the reference of the callback that you are passing to the useAsyncOperation
.
The way how to determine when you have to use useCallback
or not for functions is to check if that function is tracked as dependency. And your custom hook tracks operation
in useCallback
, and the result is also tracked in useEffect
.
If you do not keep the reference of your operation
callback, fire
will get a new reference on each render, since it tracks operation
. And this will lead to trigger useEffect
as well, since it tracks fire