23

When an event handler uses this (juet like handleClick below uses this.setState), you have to bind the event handler with this kerword. Otherwise, you need to use the arrow function.

e.g.

//This function isn't bound whilst using "this" keyword inside of it.
//Still, it works because it uses an arrow function
handleClick = () => {
    this.setState({
      isClicked:true
    });
}

render() {
    return (
      <div className="App">
        <button onClick={this.handleClick}>Click</button>
      </div>
    );
}

However, with the approach above, you can't pass a parameter. You need to use either...

  1. bind(this, param) after the function
  2. the anonymous arrow function

i.e.

<button onClick={this.handleClick}>Click</button>
will be
<button onClick={this.handleClick.bind(this, 111)}>Click</button>
or
<button onClick={() => this.handleClick(111)}>Click</button>

Here is the question.

What is the most efficient way to pass a parameter to an event handler?

According to the official doc, using bind() can undermine the performance, because...

Using Function.prototype.bind in render creates a new function each time the component renders

The same goes for using the anonymous arrow function. The doc says that...

Using an arrow function in render creates a new function each time the component renders

Then, what will be the most efficient way to pass a parameter?

Any input will be appreciated.

PS

Some people have asked how param is determined. This will be determined dynamically (i.e. not always 111). So, it can be from states, props or some other functions defined in this class.

Hiroki
  • 3,893
  • 13
  • 51
  • 90
  • Where does the `param` come from? The answer will depend on that. – Bergi Oct 13 '18 at 11:55
  • 2
    Note that the overhead of creating a new function is (usually) not the problem by itself. More often than not, the real problem is that passing a new function into a child `PureComponent` or its equivalent would make the child's `shouldComponentUpdate` return true and cause a re-render which otherwise might not have happened. – Quassnoi Oct 13 '18 at 12:56
  • @Bergi The param is dynamically determined. It can be from states, props or another function defined in the class. – Hiroki Oct 13 '18 at 23:59
  • @Hiroki If ti comes from props, you can bind in the constructor once (or use the arrow function). If it comes from the state and every re-render has a different value anyway, then you can just bind in the `render` method. – Bergi Oct 14 '18 at 10:50
  • 1
    @Hiroki, do yourself a favor and learn the right way. The answer you accepted here is no different than using an arrow function or bind in your callback. Just check my answer and see the examples. I don't care which answer you accepted here, I just want to "shout" the truths. – devserkan Oct 19 '18 at 23:37

4 Answers4

12

Instead of .binding or creating an anonymous arrow function in render(), you can create the bound/anonymous function outside of render(), such as on the instantiated object, in the constructor, or something like that, and use a reference to that singular (never re-created) function. For example, run once:

this.boundHandleClick = this.handleClick.bind(this, 111);

or

this.boundHandleClick = () => this.handleClick(111);

Then, in render, reference boundHandleClick:

return (
  <div className="App">
    <button onClick={this.boundHandleClick}>Click</button>
  </div>
);

If you need to use the parameters (111) inside of render, then you could use object lookup to see if a bound function with that parameter exists yet. If it does, just use that bound function - else, create it (once, so it won't have to be created again whenever you use that parameter in the future):

this.boundClicks = {};
// ...
if (!this.boundClicks['111']) this.boundClicks['111'] = () => this.handleClick(111);
return (
  <div className="App">
    <button onClick={this.boundClicks['111']}>Click</button>
  </div>
);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 4
    **This is the right answer!** Bottom line: Avoid declaring arrow functions or binding in render for optimal performance. Declare your functions outside of `render`. No more function allocations on each render. – Dženis H. Oct 13 '18 at 01:13
  • 1
    @devserkan OP was concerned about the performance implications of creating new functions each time `render()` occurs - this is a way around that, which only creates functions when necessary, rather than every time. – CertainPerformance Oct 13 '18 at 01:14
  • Yeah, my answer was to a deleted comment not to you :) In that one one said quesion is about comparing binding to arrow functions. – devserkan Oct 13 '18 at 01:16
  • Accurate, but in most use cases I've seen you don't have the value you want to pass in yet when you're binding in the constructor. A lot of the time you're passing in something that's not calculated until the render happens, or something like a value coming from a context provider. Not sure there's any way around binding in the render, a lot of the time. – Steve Archer Oct 13 '18 at 01:16
  • 1
    I agree on @SteveArcher here. Isn't it very hard most of the time doing something like that? Parameters are generally dynamic and many. – devserkan Oct 13 '18 at 01:18
  • React team does not want to confuse people probably :) One can change the logic and avoid using those two options. There would be a separate component which gets the row and render it. So, `id` comes with the props and can be used in a second handler function. See my answer. But, it requires much coding :) – devserkan Oct 13 '18 at 01:31
  • @NguyễnThanhTú, check my updated answer why I insisted on this so much :) – devserkan Oct 13 '18 at 05:18
  • *"...you can create the bound/anonymous function outside of render()`..."* Typically in the constructor, may be worth pointing that out / providing a complete example via [a React+JSX Snippet](http://meta.stackoverflow.com/questions/338537/). – T.J. Crowder Oct 13 '18 at 11:09
10

This depends on how you get the parameter. There will be times you can't avoid using a .bind or an arrow function easily but most of the times you can get the parameter somehow. As you can see in @CertainPerformance's answer if you can use this argument in the constructor, you can prefer this way. But there can be other approaches.

For example, assume that you have a list in the state. Instead of mapping this list directly and using a .bind or an arrow function there, you can pass the list elements to a child component and then use a callback handler there.

class App extends React.Component {
  state = {
    list: [ "foo", "bar" ],
  };

  handleClick(el) { console.log( el ) }

  render() {
    return (
      <div>
        {this.state.list.map( el => (
          <Child key={el} el={el} onClick={this.handleClick} />
        ) )}
      </div>
    );
  }
}

const Child = ( props ) => {
  const handleClick = () => props.onClick( props.el );
  return (
    <div>
      {props.el}
      <button onClick={handleClick}>Click</button>
    </div>
  );
};

ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

I am updating my answer with a demonstration how using an inline arrow function, or binding it, or using a curried function causes a recreation here.

Assume that you have a component and this component has a child component written as React.PureComponent. Normally, if this child's props does not change it won't re-render. Cool. We have a class method in our parent component and want to pass this as a handler to our child component. Let's see what is going on here.

First, I don't pass the handler and when you increment the counter in the parent, child component does not rerender again (except the initial render). This is because we defined it as a PureComponent. We don't want it to be rerendered unless its props changes.

class App extends React.Component {
  state = {
    counter: 0,
  };

  increment = () =>
    this.setState( currentState => ( {
      counter: currentState.counter + 1,
    } ) );

  handleClick(param) { console.log( param ) }

  render() {
    return (
      <div>
        <button onClick={this.increment}>Increment</button>
        Counter is: {this.state.counter}
        <Child />
      </div>
    );
  }
}

class Child extends React.PureComponent {
  render() {
    console.log( "child rendered" );
    return (
      <div>
        <button onClick={this.props.onClick}>Click</button>
      </div>
    );
  }
}

ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

As you can see child component isn't rerendered. Now lets do this with our class method, using an inline arrow function.

class App extends React.Component {
  state = {
    counter: 0,
  };

  increment = () =>
    this.setState( currentState => ( {
      counter: currentState.counter + 1,
    } ) );

  handleClick( param ) { console.log( param ) }

  render() {
    return (
      <div>
        <button onClick={this.increment}>Increment</button>
        Counter is: {this.state.counter}
        <Child onClick={() => this.handleClick( "some param" )} />
      </div>
    );
  }
}

class Child extends React.PureComponent {
  render() {
    console.log( "child rendered" );
    return (
      <div>
        <button onClick={this.props.onClick}>Click</button>
      </div>
    );
  }
}

ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

Ooops, child is rendered when we increment the counter. But, it does not have any relationship to the counter state, we don't want this. So why is it rerendering? This is because we are using an inline arrow function in the onClick prop it is getting. Since this function is recreated in every render of the parent, its reference changes to a different function and child thinks that it gets a new prop! But in reality it does not get it. We can use the parameter with our handler but there is unnecessary rendering.

Now with the .bind. I don't use this in the bind since we don't use this in our simple method. It just logs a parameter.

class App extends React.Component {
  state = {
    counter: 0,
  };

  increment = () =>
    this.setState( currentState => ( {
      counter: currentState.counter + 1,
    } ) );

  handleClick( param ) { console.log( param ) }

  render() {
    return (
      <div>
        <button onClick={this.increment}>Increment</button>
        Counter is: {this.state.counter}
        <Child onClick={this.handleClick.bind( null, "some param" )} />
      </div>
    );
  }
}

class Child extends React.PureComponent {
  render() {
    console.log( "child rendered" );
    return (
      <div>
        <button onClick={this.props.onClick}>Click</button>
      </div>
    );
  }
}

ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

Same here, we can use the parameter but there is unnecessary rendering. Now with a curried function.

class App extends React.Component {
  state = {
    counter: 0,
  };

  increment = () =>
    this.setState( currentState => ( {
      counter: currentState.counter + 1,
    } ) );

  handleClick( param ) { 
    return function() {
      console.log( param ) 
    }
  }

  render() {
    return (
      <div>
        <button onClick={this.increment}>Increment</button>
        Counter is: {this.state.counter}
        <Child onClick={this.handleClick( "some param" )} />
      </div>
    );
  }
}

class Child extends React.PureComponent {
  render() {
    console.log( "child rendered" );
    return (
      <div>
        <button onClick={this.props.onClick}>Click</button>
      </div>
    );
  }
}

ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

Surprise! Again unnecessary rendering. Now, for one component this is not so important. But what if your app has hundereds of components like this child?

Now, lets assume I'm getting the param somehow. I am mimicking it with a hardcoded string here.

class App extends React.Component {
  state = {
    counter: 0,
  };

  increment = () =>
    this.setState( currentState => ( {
      counter: currentState.counter + 1,
    } ) );

  handleClick() { console.log( "some param" ) }

  render() {
    return (
      <div>
        <button onClick={this.increment}>Increment</button>
        Counter is: {this.state.counter}
        <Child onClick={this.handleClick} />
      </div>
    );
  }
}

class Child extends React.PureComponent {
  render() {
    console.log( "child rendered" );
    return (
      <div>
        <button onClick={this.props.onClick}>Click</button>
      </div>
    );
  }
}

ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

Duh! No unnecessary rerendering as expected since we used the function reference. I can use the param but life is not so easy and OP actually asking how can we use the parameter without using an inline arrow function, or binding or with a curried function. All the fuss is about this.

Even though we don't pass this handler to a component down, it is still recreated in every render of the parent as we see here. If you have a list of items, lets say 500 of them, and you are mapping them into the buttons in the parent component and use an arrow function, etc here, this means they will be recreated (500 times) in every render!

So, there isn't any easy way of doing this. If our parameter is not coming from the event object then either we use @CertainPerformance's solution or try to change our logic like I do here.

devserkan
  • 16,870
  • 4
  • 31
  • 47
  • Hey, I found something for us, a question in 2015, three years ago we already had an answer: https://stackoverflow.com/questions/29810914/react-js-onclick-cant-pass-value-to-method – You Nguyen Oct 13 '18 at 10:39
  • 1
    Hi. As you can see, all the suggestions are similar and they mention about recreating issues (those for inline arrow function, bind or even there is a currying one!). The most probable solution is extracting the component as I do here :) But, here @CertainPerformance's answer is as a plus. – devserkan Oct 13 '18 at 10:53
  • Have you read this: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md , I just found it – You Nguyen Oct 13 '18 at 11:16
  • Of course :) My answer depends on it :D Extracting the component like I do or the old answer you provided here. – devserkan Oct 13 '18 at 11:19
  • Don't use an arrow function if you don't access `this`. – Bergi Oct 13 '18 at 11:57
  • @Bergi, you are right it is unnecessary here. Altough it is just an example I should have avoid it. I will update my answer when I get home. Thanks. – devserkan Oct 13 '18 at 12:22
10

I have explained it in my another post: click event in react component.

Never use inline arrow function if you're worried about its performance. You can still use the public class method and bind the context this.

handleClick = () => () => {
    this.setState({ // this works now
      isClicked:true
    });
}

You can pass any parameters you like just like this:

handleClick = (param1, param2, param3) => (event) => {

As per devserkan's comment,

This is currying and same as other options. This function is also recreated in every render.

No. It doesn't. See the note from docs:

If this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the class fields syntax, to avoid this sort of performance problem.

Also, see the comment from bigga-hd below the certainperformance's answer:

Avoid declaring arrow functions or binding in render for optimal performance. Declare your functions outside of render. No more function allocations on each render.


How do you call this handler?

You can call the method just like this:

onClick={this.handleClick(param1,param2,param3)}

PS: I did not mark this post as duplicate as question scope is significantly different. So, just linked the post to get you dig into more detail.

Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
  • 5
    This is currying and same as other options. This function is also recreated in every render. – devserkan Oct 13 '18 at 02:17
  • No to which one? Isn't it currying or isn't it recreated in every render? How do you use this function in the onclick handler? – devserkan Oct 13 '18 at 02:19
  • 5
    I can see you've updated the answer but this does not answer my question. Docs do not say anything about currying. This is currying If I'm not mistaken. So, you are using the curried function in the callback handler and it is recreated in every render. – devserkan Oct 13 '18 at 02:26
  • I read it carefully. In the example they use a normal function but they defined it in the public class field. I am accustomed to class fields, just be sure. But the difference here is you are using a **curried** function, they do not. And you are using this function in the callback. So, it is recreated. This is not different using bind or an arrow function. You are just currying it. Whenever we see `()` in the handler this means this function is recreated in every render. – devserkan Oct 13 '18 at 02:32
  • You're misunderstanding. The use of public class method is to avoid **re-rendering** but it doesn't mean using curry function will cause on performance. – Bhojendra Rauniyar Oct 13 '18 at 02:33
  • 6
    Maybe you are the one that misunderstanding it :) Public class fields suggestion is just an alternative to binding, that is all. It is not related to rerendering. You can define this function not in the class field but in the constructor also, with an arrow function. The issue is not class fields here. Again, you are using a **curried** function as callback handler. It is recreated (does not related rerendering). Using a curried function is similar to using an arrow function. – devserkan Oct 13 '18 at 02:41
  • Ok, I will try my best :) If we use an arrow function or a bind in the callback handler, even the component itself does not change (so no rerender) but the function is recreated, so most of the time we pass this callback to a component, then these components are rerendered since the function is recreated and props changed. This is where we are. I and you are agreed on this, OK. This is why we define functions in the class or outside of render for a functional component and use its reference. – devserkan Oct 13 '18 at 02:52
  • But, again, but using a curried function here has the same effect as using an arrow function or binding it in the callback. Believe me, it is recreated in every render and since the function is changed children are rerendered again. Currying does not avoid this. – devserkan Oct 13 '18 at 02:53
  • Not at all. Re-creating the function doesn't re-render the component. Sorry, I'm stopping now(I am not able to make you clear). – Bhojendra Rauniyar Oct 13 '18 at 02:53
  • So why do we avoid it using an arrow function or bindin it directly in the callback? Here, from the documentation you are quoting: `The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering. We generally recommend binding in the constructor or using the class fields syntax, to avoid this sort of performance problem.` – devserkan Oct 13 '18 at 02:55
  • ha, that says callback is created not function inside function. Just closing my opinions. – Bhojendra Rauniyar Oct 13 '18 at 02:56
  • 7
    Ok, let's stop. But it is sad seeing this. Suggesting a curried function over an arrow one or binding. It is the **same** thing. In the documentation, they don't use a curried function this is why it works. Whenever you see `()` in the callback as you used, this means callback is recreated and since it is recreated components which accept this think as a prop changed and they are rerendered. – devserkan Oct 13 '18 at 02:59
  • 4
    Again, doing this in either way depends on the person who codes their app. But, I am arguing a different thing. What is being suggested here is not true. Either OP uses or not. Maybe @BhojendraRauniyar wants to check Jordan Harband's answer: https://github.com/airbnb/javascript/issues/659#issuecomment-222519884 – devserkan Oct 13 '18 at 03:07
  • And if someone sees those comments and digs the issue, If you create a curried function and use it with the **reference** this is ok but if you are using it with a parameter like `foo(one, two)`, this is no different using an inline arrow function. You can read the discussion I provided above. – devserkan Oct 13 '18 at 03:18
  • 1
    I'm writing this comment in a sincere manner. Just check my updated answer and see that using a curried function does not matter for this special situation. – devserkan Oct 13 '18 at 05:20
  • 1
    Recreating the function doesn't cause are-render, but a re-render will create a new function when it calls handleClick, which will happen on every render. You've just moved the function creation out of render and into a utility function. – Dave Newton Oct 14 '18 at 00:43
  • @DaveNewton, I also said that this does not trigger a rerender in the parent component but it **can** trigger unnecessary rerenders in the child components. I also provided examples of this situation in my answer. But, what I'm trying to say here, using a curried function and pass a parameter to it in the callback function is no different than using an arrow function or bind there. So, curried functions (without using the reference) is not an alternative to OP's options. Also, I can't believe this one is the accepted answer now. – devserkan Oct 19 '18 at 23:25
  • @devserkan And I'm agreeing with you. – Dave Newton Oct 20 '18 at 00:36
  • Oh, sorry. I misunderstood you totally then :) – devserkan Oct 20 '18 at 00:39
  • Thanks for the answer, But what's with () => () =>{} ? normally there is just one () in an arrow function. Here, why do we need two () one for parameter and another for event. Can't we use single () for both? – siddharth shah Oct 21 '20 at 10:54
  • This is the most confusing answer. It's half right and half not. Use with caution! – Mrchief Mar 18 '22 at 16:10
0

I used to do this kind of thing for state names. Depends on what you want, you can try this. So that, i don't have to bind the function again. Where you can get state name from e.target.name

class App extends React.Component {
  state = {
    change: ""
  };

  handleChange = e => {
    const { name, value } = e.target;
    this.setState({ [name]: value  })
  };

  render() {
    return (
      <input
        type="text"
        onChange={this.handleChange}
        name="change"
        value={this.state.change}
      />
    );
  }
}
Kyaw Siesein
  • 705
  • 8
  • 14