score:66

Accepted answer

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.


Related Query

More Query from same tag