Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Should I use memoization or callback when passing a function as parameter of a custom hook?

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 ?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading