score:4

Accepted answer

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}}/>

score:0

One possible way of doing so is by creating a custom hook: (Don't take this example too seriously, just to make a point).

const useFactory = (type) => {
    let f;
    if (type === 'a') {
    f = () => console.log('hello world');
  } else {
    f = () => console.log('bye world');
  }

  return React.useCallback(f, [type]);
};

const Button = React.memo(({ onClick, children }) => {
    console.log('re render');
    return <button onClick={onClick}>{children}</button>;
});

const App = () => {
  const f1 = useFactory('a');
  const f2 = useFactory('b');

  return (
    <div>
      <Button onClick={f1}>F1</Button>
      <Button onClick={f2}>F1</Button>
    </div>  
  );

};

https://jsfiddle.net/kadoshms/740zkeso/9/

score:0

I think you are close with useCallback but you really just want to memoize the result of the factory. The useMemo hook can do that.

const callback = useMemo(() => {
  return callbackFactory(param);
}, [param]);

The hook won't recompute its return value (i.e. callback) unless the param in the dependency array changes.

Demo

const callbackFactory = param => {
  console.log("invoked callback factory!!");
  switch (param) {
    case "a":
      return () => console.log("callback A");

    case "b":
      return () => console.log("callback B");

    default:
      return () => {};
  }
};

export default function App() {
  const [param, setParam] = useState("a");
  const [, setCount] = useState(0);

const callback = useMemo(() => {
  return callbackFactory(param);
}, [param]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>

      <select defaultValue="a" onChange={e => setParam(e.target.value)}>
        {["a", "b"].map(param => (
          <option key={param} value={param}>
            {param}
          </option>
        ))}
      </select>

      <button type="button" onClick={callback}>
        Press Me
      </button>
      <button type="button" onClick={() => setCount(c => c + 1)}>
        Rerender!
      </button>
    </div>
  );
}

Edit memoized callbacks from factory

score:1

This is very unfortunate, but react doesn't support something like this. The best way I found would be to reuse the same memoized callback and use name as the id, as follows (modifying your code snippet):

function MyComponent() {

    const callback = useCallback(e => {
        const param = e.target.name
        /* perform action with 'param' */
    }, []);

    return (
        <>
            <Button name='foo' onClick={callback} />
            <Button name='bar' onClick={callback} />
            <Button name='bar' onClick={callback} />
        </>
    );
}

Likewise in complex scenarios, you can pass some sort of id in the child where the child can pass it back in the callback:

function Parent() {
    const callback = useCallback((e, param) => {
        /* perform action with 'param' */
    }, []);
    return <>
        <Child id='foo' onChange={callback} />
        <Child id='bar' onChange={callback} />
        <Child id='baz' onChange={callback} />
    </>
}

const Child = memo(({id, onChange}) => {
    return <MyComponent onChange={e => onChange(e, id)} />
})

Although at its face you seem to create a new function in the child, the (memoized) child itself will not re-render on each parent render.

score:2

If the callbacks should remain the same between different "instances" of the component and remounts of the component it may make sense to declare them in advance out of the scope of the component.

const cb1 = params => { ... };
const cb2 = params => { ... };

const App = () => (
  <div>
    <Button onClick={cb1}>F1</Button>
    <Button onClick={cb2}>F2</Button>
  </div>  
);

This is not very handy, so it may make sense to memoize the results factory returns and reuse it.

const makeMemoizedCallback = factory => {
  let cache = {};
  return (...args) => {
    const key = JSON.stringify(args);
    if (!(key in cache)) {
      cache[key] = factory(...args);
    }
    return cache[key];
  };
};

const makeCallback = makeMemoizedCallback(param => runtime => {
  console.log(param, runtime);
});

console.log(makeCallback("a") === makeCallback("a"));
makeCallback("a")(1);
makeCallback("b")(2);

Usage:

const makeButtonClickHandler = makeMemoizedCallback(param => e => {
  console.log(param, e);
});

const App = () => (
  <div>
    <Button onClick={makeButtonClickHandler("F1")}>F1</Button>
    <Button onClick={makeButtonClickHandler("F2")}>F1</Button>
  </div>
);

Related Query

More Query from same tag