Skip to content
Advertisement

On an onClick event in a React function component, should I pass a function by reference or make an anonymous function?

I found this question but it doesn’t answer my specific case, because the answers are old, don’t answer all my questions in this post and in that case they only pass the event variable to the function, but in my case I might want to pass a different type of value, like a string: React performance: anonymous function vs named function vs method

In React, on a function component, let’s say I have a button:

return <button>Click me</button>

I want to add an onClick event to it, but instead of passing the mouse event to the function, I want to send other kinds of data. What’s the correct (or even better, the most performant) pattern?

The use case would be a grid with hundreds of cells, and every cell has an onClick event. I guess that option b would be better because we’re only passing a reference, right? Does modern React have some kind of optimizations for this use case?

a)

const handleClick = (value) => console.log(value);
return <button onClick={() => handleClick('hello')}>Click me</button>

b)

const handleClick = (value) => () => console.log(value);
return <button onClick={handleClick('hello')}>Click me</button>

Advertisement

Answer

  1. b) unless your handleClick function returns a new function (which will act like the first example) it won’t work because all you’re doing is assigning the result of calling that function to the listener rather than the reference to the function.

  2. a) will work but there’s a lot of documentation around to suggest that rebinding arrow functions on each render leads to poor performance.

  3. I prefer to do onClick={fn} and then define the function either within the component, or outside it, or maybe I’ve imported it. Either way I think it makes it more readable. But that’s an opinion, and many developers have different approaches.

  4. But the main issue is: don’t attach an click listener to every single one of the cells! That most likely will lead to a performance issue. Instead: a) add one listener to a containing element (table, for example), and then, using event delegation watch for events from the cells as the “bubble up” the DOM. b) Use Data attributes to attach specific data to the cells, and pick up their data in the click handler from the dataset.

So: a contrived example to show you those last points:

function Example() {

  // Because we're using event delegation
  // check to see if the element we clicked on
  // was actually a table cell, then grab the id
  // from its dataset
  function handleClick(e) {
    if (e.target.matches('td')) {
      const { id } = e.target.dataset;
      console.log(id);
    }
  }

  // One listener to rule them all
  return (
    <table onClick={handleClick}>
      <tbody>
        <tr>
          <td data-id="1">One</td>
          <td data-id="2">Two</td>
          <td data-id="3">Three</td>
        </tr>
      </tbody>
    </table>
  );

}

ReactDOM.render(
  <Example />,
  document.getElementById('react')
);
table { border: 1px solid #565656; border-collapse: collapse; }
td { border: 1px solid #787878; padding: 0.3em; }
td:hover { cursor: pointer; background-color: #dfdfdf; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement