45

In a typical class-based React component, this is how I would create an event handler:

class MyComponent extends Component {
  handleClick = () => {
    ...
  }

  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

However, I find myself with two options when I use a hooks-based functional paradigm:

const MyComponent = () => {
  const [handleClick] = useState(() => () => {
    ...
  });

  return <button onClick={handleClick}>Click Me</button>;
};

or alternatively:

const MyComponent = () => {
  const handleClick = useRef(() => {
    ...
  });

  return <button onClick={handleClick.current}>Click Me</button>;
};

Which one is objectively better, and for what reason? Is there another (better) way that I have not yet heard of nor discovered?

Thank you for your help.

Edit: I have put an example here on CodeSandbox showing both methods. Neither seems to unnecessarily recreate the event handler on each render, as you can see from the code on there, so a possible performance issue is out of the question, I think.

Lucas
  • 16,930
  • 31
  • 110
  • 182
  • 2
    You don't need to use a hook or classical components to create functions within the component. You can still declare a const function either inside the body of the functional component, or outside. – Richard Mar 05 '19 at 11:38

1 Answers1

66

I wouldn't recommend either useState or useRef.

You don't actually need any hook here at all. In many cases, I'd recommend simply doing this:

const MyComponent = () => {
  const handleClick = (e) => {
    //...
  }

  return <button onClick={handleClick}>Click Me</button>;
};

However, it's sometimes suggested to avoid declaring functions inside a render function (e.g. the jsx-no-lambda tslint rule). There's two reasons for this:

  1. As a performance optimization to avoid declaring unnecessary functions.
  2. To avoid unnecessary re-renders of pure components.

I wouldn't worry much about the first point: hooks are going to declare functions inside of functions, and it's not likely that that cost is going to be a major factor in your apps performance.

But the second point is sometimes valid: if a component is optimized (e.g. using React.memo or by being defined as a PureComponent) so that it only re-renders when provided new props, passing a new function instance may cause the component to re-render unnecessarily.

To handle this, React provides the useCallback hook, for memoizing callbacks:

const MyComponent = () => {
    const handleClick = useCallback((e) => {
        //...
    }, [/* deps */])

    return <OptimizedButtonComponent onClick={handleClick}>Click Me</button>;
};

useCallback will only return a new function when necessary (whenever a value in the deps array changes), so OptimizedButtonComponent won't re-render more than necessary. So this addresses issue #2. (Note that it doesn't address issue #1, every time we render, a new function is still created and passed to useCallback)

But I'd only do this where necessary. You could wrap every callback in useCallback, and it would work... but in most cases, it doesn't help anything: your original example with <button> won't benefit from a memoized callback, since <button> isn't an optimized component.

Retsam
  • 30,909
  • 11
  • 68
  • 90
  • 4
    `your original example with – Kostiantyn Ko May 03 '19 at 03:47
  • 1
    @KostiantynKolesnichenko The main point of those optimizations is to avoid the expense of component *render* logic, and there's no render function for a DOM element - it essentially just produces a static object that represents the element. React will need to update the DOM, but the cost of flipping an event handler is almost certainly trivial. But the cost of rerendering an entire new component because a function instance changed can be an unnecessary expense. – Retsam May 15 '19 at 21:16
  • 1
    Does the reconciler really touch the DOM when it replaces event handlers? – thorn0 Sep 03 '19 at 19:34
  • 1
    @thorn̈ Yeah, I believe the answer is no; React uses synthetic events and delegates them to your custom handler functions. – Retsam Sep 04 '19 at 02:10