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.
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:
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.