Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return a React memoized callback from factory function

I am wondering how best to return a memoized callback function in React when using a factory to generate that callback. The goal is to return the same function instance when the factory is called with the same parameter.

For example:

function MyComponent() {
    // This will always return a new function
    const makeCallback = param => () => {
        /* perform action with 'param' */
    };

    return (
        <>
            <Button onClick={makeCallback('foo')} />
            <Button onClick={makeCallback('bar')} />
            <Button onClick={makeCallback('baz')} />
        </>
    );

I don't believe wrapping the factory itself with a useCallback would provide any benefit, since that function is not actually being passed to any of the children, so my idea was to return a useCallback function from the factory.

Like this:

const makeCallback = param => React.useCallback(
    () => {
        /* perform action with 'param' */
    },
    [param]
);

However, that was not allowed and failed at build time.

React Hook "React.useCallback" is called in function "makeCallback" which is neither a React function component or a custom React Hook function - react-hooks/rules-of-hooks

The "Rules of Hooks" say clearly that calling a hook in a nested function is not permitted, but this seems odd to me since a custom hook is often literally just a function that calls other hooks. It says the primary concern is preserving the order of execution, but I don't think that would be violated in this case.

Is my best option to turn my factory into a hook and call it explicitly at the top level for each case? I'd prefer the simplicity of building the callback in the button itself, since it's a little less typing and the param piece is more apparent and obvious when kept with the button.

// Same factory function, but now it's magically a "hook"
const useCallbackFactory = param => {
    return React.useCallback(() => { /* do 'param' stuff */ }, [param]);
}

function MyComponent() {
    // Define the callbacks ahead of time
    const fooCb = useCallbackFactory('foo');
    const barCb = useCallbackFactory('bar');
    const bazCb = useCallbackFactory('baz');

    return (
        <>
            <Button onClick={fooCb} />
            <Button onClick={barCb} />
            <Button onClick={bazCb} />
        </>
    );
}
like image 809
J. Sheets Avatar asked Apr 28 '20 06:04

J. Sheets


People also ask

What is memoized callback React?

The React useCallback Hook returns a memoized callback function. Think of memoization as caching a value so that it does not need to be recalculated. This allows us to isolate resource intensive functions so that they will not automatically run on every render.

What does useCallback return?

useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate ).

How do you Memoize a function in React?

Memoization Using Memo 1 2 3 const MyComponent = React. memo(function MyComponent(props) { /* render using props */ }); We can make a custom comparison function and pass it as a second argument to memo to control its default comparison behavior, which shallowly compares complex objects in the props object.


1 Answers

The goal is to return the same function instance when the factory is called with the same parameter.

I think what you want is something like this:

 let memoizedcb = React.useCallback(
    memoize((fieldName) => (val) => doSomething(fieldName, val)),
    []
  );

where

import memoize from "fast-memoize";

Now function returned by memoizedcb will be same across renders for the same argument.

Now you can use it like

<TextField onChange={memoizedcb("name")} value={name}/>
<TextField onChange={memoizedcb("surname")} value={surname}}/>
like image 152
Giorgi Moniava Avatar answered Sep 20 '22 17:09

Giorgi Moniava