React hooks useCallback with parameters inside loop

The simple answer here is, you probably shouldn’t use useCallback here. The point of useCallback is to pass the same function instance to optimized components (e.g. PureComponent or React.memoized components) to avoid unnecessary rerenders.

You’re not dealing with optimized components in this case (or most cases, I’d suspect) so there’s not really a reason to memoize callbacks like with useCallback.


Supposing the memoization is important, though, the best solution here is probably to use a single function instead of five: instead of a unique function for each button carries the key by closure, you can attach the key to the element:

<button data-key={key}>{key}</button>

And then read the key from the event.target.dataset["key"] inside a single click handler:

const Example = (props) => {
  // Single callback, shared by all buttons
  const onClick = React.useCallback((e) => {
    // Check which button was clicked
    const key = e.target.dataset["key"]
    console.log("You clicked: ", key);
  }, [/* dependencies */]);
  
  return(
    <div>
      {
        _.times(5, (key) => {
          return (
            <button data-key={key} onClick={onClick}>
              {key}
            </button>
          );
        })
      }
    </div>
  );
};
console.log("hello there");

ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id='root'>
</div>

All that being said, it is possible to memoize multiple functions in a single hook. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps), and useMemo can be used to memoize multiple functions at once:

const clickHandlers = useMemo(() => _.times(5, key => 
  () => console.log("You clicked", key)
), [/* dependencies */]);

const Example = (props) => {
  const clickHandlers = React.useMemo(() => _.times(5, key => 
    () => console.log("You clicked", key)
  ), [/* dependencies */])
  
  return(
    <div>
      {
        _.times(5, (key) => {
          return (
            <button onClick={clickHandlers[key]}>
              {key}
            </button>
          );
        })
      }
    </div>
  );
};
console.log("hello there");

ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id='root'>
</div>

Perhaps there’s a case where that’s useful, but in this case I would either leave it alone (and not worry about the optimization) or else use a single handler for each button.

Leave a Comment

tech