Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to create event handlers using hooks in React?

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.

like image 460
Lucas Avatar asked Mar 05 '19 11:03

Lucas


1 Answers

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.

like image 56
Retsam Avatar answered Sep 30 '22 15:09

Retsam