0

I'm still pretty new to React, but I realize that there are two common ways to define callback functions:

// Method 1
private handleInputChange = (event) => {
    this.setState({name: event.target.value});
}
// Method 2
private handleInputChange(event){
    this.setState({name:event.target.value});
}

And inside the DOM element (for this example let's use <input>), the handler can be called via different methods like:

// Method 1
<input
    type="text"
    onChange={this.handleInputChange}
/>

// Method 2
<input
    type="text"
    onChange={(e) => this.handleInputChange(e)}
/>

// Method 3 (assume the function don't have parameters)
<input
    type="text"
    onChange={this.handleNewThing()}
/>

My question is, which is the proper method to be used? I'm genuinely so confused between all these methods. And then I see people including bind() for some reason??

avhhh
  • 229
  • 1
  • 3
  • 11
  • if you pass a callback to onChange (eg: onChange={handleChange}), then you need to make sure that function is bound to the current component's scope (using this.handleChange.bind(this) in the constructor). This is probably the most efficient way to attach event handlers as well, since we are not creating a new function each time the render method is called (not the case with handlers like onChange={e => handleChange(e)}) – Brian May 06 '20 at 21:05
  • @Brian What do you mean by creating a new function each time the render method is called? – avhhh May 06 '20 at 21:09
  • 1
    When the render method is called on a react component, any lambda functions you define as event handlers (like onChange={e => handleChange(e)}) will be evaluated. So, every time the render method gets called, new functions are created. – Brian May 06 '20 at 21:12
  • 1
    I see. If the function requires parameters, then how can you pass parameters using the method: (onChange={handleChange})? Additionally, this method works without me having to bind the function, so why is it necessary to use `this.handleChange.bind(this)` in the constructor? – avhhh May 06 '20 at 21:16
  • 1
    It depends on how you create the callback function. If you create the function using function handleChange(), then you will need to bind the function to the component's scope using .bind. However, if you create handleChange as a lambda function (eg: Method 1) you do not need to bind the function to the component's scope, as lambda functions are already bound to their parent's scope. – Brian May 06 '20 at 21:17
  • In many cases, you can simply pass a single object as the argument to the function (this also makes it easier to refer to parameters by name, instead of position). You can also access the implicit `arguments` object within a function to access its arguments – Brian May 06 '20 at 21:21
  • 1
    For example, the onChange event only passes a single argument to its handler - a synthetic react change event, which is a proxy to the actual change event. If you need to pass multiple arguments to a change handler, you might be better off using a lambda function (or you can simply .bind the additional arguments on each render) – Brian May 06 '20 at 21:25
  • 1
    The onChange={handleChange} style passes only one argument to the handleChange function, but in most cases that's all you need. – Brian May 06 '20 at 21:27
  • 1
    If I'm understanding it correctly, it's better to use `onChange={handleChange}` if the function only needs the event parameter that is passed implicitly through this call. If I am adding multiple additional parameters, I can use a lambda function and set the callback like `onChange={(e) => handleChange(e, msg)}`? Where it's defined like: `handleChange = (e, msg) => {...}` – avhhh May 06 '20 at 21:30
  • Yes, that's correct. That would be the approach to use. – Brian May 06 '20 at 21:55
  • @Brian Thank you so much for the detailed explanation! – avhhh May 06 '20 at 23:21
  • Note that the recommended way to write new code is using [Hooks](https://reactjs.org/docs/hooks-intro.html). – tokland May 06 '20 at 21:05

1 Answers1

2

You’re right, there are a bunch of ways to handle handlers (ba-dum-tss). React has been around for a while now and the face of JavaScript has changed quite a bit in that time.

There’s a whole page on handling events in the React documentation, but nevertheless here’s a comparison of some of the ways callbacks are handled:

class MyComponent extends React.Component {
  constructor (props) {
    super(props)
    this.boundHandleClick = this.boundHandleClick.bind(this)
  }

  arrowHandleClick = (event) => { this.props.onClick(event.target.id) }

  boundHandleClick (event) { this.props.onClick(event.target.id) }

  boundInRenderHandleClick (event) { this.props.onClick(event.target.id) }

  unboundHandleClick (event) { this.props.onClick(event.target.id) }

  render () {
    return (
      <div>
        <button id='zero' onClick={(event) => { this.props.onClick(event.target.id) }} />
        <button id='one' onClick={arrowHandleClick} />
        <button id='two' onClick={boundHandleClick} />
        <button id='three' onClick={boundInRenderHandleClick.bind(this)} />
        <button id='four' onClick={unboundHandleClick} />
      </div>
    )
  }
}

When clicked:

  • #zero will correctly call props.onClick. The problem with this is that the anonymous function created in the render method will be recreated on every render. This is not good for performance.

  • #one will correctly call props.onClick. Because the callback is defined as a class method, it will only be created when MyComponent is instantiated (and mounted). This is an acceptable way to define a callback. Apparently it’s slower than once thought, but it’s also the neatest in my opinion.

  • #two will correctly call props.onClick. This is essentially the same as arrowHandleClick, only it’s a bound function as opposed to an arrow function. For all intents and purposes, they’re the same - but by all means dig into the differences.

  • #three will correctly call props.onClick, and it has the same result as #two, but has the negative performance impact as #zero - functions should not be created nor bound in the render method.

  • #four will not work correctly and will throw an error. When it’s run, this will be referring to the element (in this case, #four) rather than the class instance. In all other handlers, this refers to the React class instance, which has a props.onClick.

Now there is a new accepted way of writing components: using plain functions and hooks. this is a thing of the past.

const MyComponent = (props) => {
  const handleClick = event => props.handleClick(event.target.id)

  const memoizedHandleClick = React.useCallback(
    event => props.handleClick(event.target.id), 
    [props.handleClick]
  )

  return (
    <div>
      <button id='zero' onClick={event => props.handleClick(event.target.id)} />
      <button id='one' onClick={handleClick} />
      <button id='two' onClick={memoizedHandleClick} />
    </div>
  )
}

All callbacks work correctly here - the only difference is that memoizedHandleClick will not be recreated on every render, unless props.handleClick changes. In my own experience, both are acceptable and people don’t really seem to mind about recreating callbacks in functional components anymore - but rather being pragmatic and addressing performance issues when you encounter them.

Alex Bass
  • 2,262
  • 1
  • 13
  • 16