title: useLoopCallback -- useCallback hook for components created inside a loop
body: <p>I'd like to start a discussion on the recommended approach for creating callbacks that take in a parameter from a component created inside a loop.</p>
  <p>For example, if I'm populating a list of items that will have a &quot;Delete&quot; button, I want the &quot;onDeleteItem&quot; callback to know the index of the item to delete.  So something like this:</p>
  <pre><code>  const onDeleteItem = useCallback(index =&gt; () =&gt; {
      setList(list.slice(0, index).concat(list.slice(index + 1)));
    }, [list]);
  
    return (
      &lt;div&gt;
        {list.map((item, index) =&gt;
          &lt;div&gt;
            &lt;span&gt;{item}&lt;/span&gt;
            &lt;button type=&quot;button&quot; onClick={onDeleteItem(index)}&gt;Delete&lt;/button&gt;
          &lt;/div&gt;
        )}
      &lt;/div&gt;
    ); 
  </code></pre>
  <p>But the problem with this is that onDeleteItem will always return a new function to the onClick handler, causing the button to be re-rendered, even when the list hasn't changed.  So it defeats the purpose of <code>useCallback</code>.</p>
  <p>I came up with my own hook, which I called <strong>useLoopCallback</strong>, that solves the problem by memoizing the main callback along with a Map of loop params to their own callback:</p>
  <pre><code>import React, {useCallback, useMemo} from &quot;react&quot;;
  
  export function useLoopCallback(code, dependencies) {
    const callback = useCallback(code, dependencies);
    const loopCallbacks = useMemo(() =&gt; ({map: new Map(), callback}), [callback]);
  
    return useCallback(loopParam =&gt; {
      let loopCallback = loopCallbacks.map.get(loopParam);
      if (!loopCallback) {
        loopCallback = (...otherParams) =&gt; loopCallbacks.callback(loopParam, ...otherParams);
        loopCallbacks.map.set(loopParam, loopCallback);
      }
      return loopCallback;
    }, [callback]);
  }
  </code></pre>
  <p>So now the above handler looks like this:</p>
  <pre><code>  const onDeleteItem = useLoopCallback(index =&gt; {
      setList(list.slice(0, index).concat(list.slice(index + 1)));
    }, [list]);
  </code></pre>
  <p>This works fine but now I'm wondering if this extra logic is really making things faster or just adding unnecessary overhead. Can anyone please provide some insight?</p>
  <p><strong>EDIT:</strong>
  An alternative to the above is to wrap the list items inside their own component.  So something like this:</p>
  <pre><code>function ListItem({key, item, onDeleteItem}) {
    const onDelete = useCallback(() =&gt; {
      onDeleteItem(key);
    }, [onDeleteItem, key]);
  
    return (
      &lt;div&gt;
        &lt;span&gt;{item}&lt;/span&gt;
        &lt;button type=&quot;button&quot; onClick={onDelete}&gt;Delete&lt;/button&gt;
      &lt;/div&gt;
    );
  }
  
  export default function List(...) {
    ...
  
    const onDeleteItem = useCallback(index =&gt; {
      setList(list.slice(0, index).concat(list.slice(index + 1)));
    }, [list]);
  
    return (
      &lt;div&gt;
        {list.map((item, index) =&gt;
          &lt;ListItem key={index} item={item} onDeleteItem={onDeleteItem} /&gt;
        )}
      &lt;/div&gt;
    ); 
  }
  </code></pre>