15

I have these parent and child component, I want to pass click function to select an item in child component. Yet it seems the function in child component become automatically called instead of waiting until the user click the element. To make it clearer here is my parent and child components

export class ParentView extends Component {
  state = {
    selectedItem: {}
  }

  handleClick = (item) => {
    alert('you click me');
    this.setState({selectedItem: item});
  } 

  render() {
    let item = { name: 'Item-1' };
    return (
      <div>
        <ChildItem item={item} handleClick={this.handleClick} />
      </div>
    );
  }
}

export class ChildItem extends Component {
  render() {
    const {item, handleClick} = this.props;
    return (
      <div>
        <a  onClick={handleClick(item)} />
      </div>
    );
  }
}

Those are my components using arrow function to pass handleClick to child component, yet alert always being called at first render without being triggered by user. Any suggestion?

Dion Dirza
  • 2,575
  • 2
  • 17
  • 21

3 Answers3

19

You should pass a function itself to onClick, not a result of the passed function invocation.

If you would like to invoke it with param, you have options:

  • bind it with item with handleClick.bind(this, item). bind creates a new function will have a predefined first parameter - item
  • pass new arrow function like () => handleClick(item)

An example below:

export class ChildItem extends Component {
  render() {
    const { item, handleClick } = this.props;
    
    return (
      <div>
        <a onClick={() => handleClick(item)} />
      </div>
    )
  }
}

In your code you're invoking a function in onClick declaration, so the result of handleClick execution will be passed to onClick, what is most likely not something you wanted to achieve.

<a onClick={handleClick(item)} />

Update:

as @dhilt wrote, there is a drawback of such approach. Since the newly created arrow function and .bind also creates new function every time the render method of ChildItem is invoked, react will threat the resulted react element as a different, comparing to the previous "cached" result of render method, that means that likely it might lead to some performance problems in the future, there is even a rule regarding this problem for eslint, but you shouldn't just follow this rule because of two points.

  1. performance problems should be measured. we don't forbid using Array.prototype.forEach in favor of a regular for because for is the same or "faster".

  2. definition of click handlers as class properties leads to increasing of the initializing step of the component instance. Re-render is fast and efficient in react, so sometimes the initial rendering is more important.

Just use what's better for you and likely read articles like this https://medium.com/@ryanflorence/react-inline-functions-and-performance-bdff784f5578

uladzimir
  • 5,639
  • 6
  • 31
  • 50
  • 5
    thanks, I solve it with `onClick={() => handleClick(item)}` – Dion Dirza Aug 30 '16 at 12:36
  • 1
    What I don't get here is how the item gets passed to handleClick since the parameters are empty for the arrow function. How is that working? if I had onClick = {this.handleClick} and a function: handleClick = element => {//stuff} How does element ever get passed in? – Accribus Jun 26 '19 at 20:22
  • @Accribus element itself (I presume you're referring to react element) will not be passed for `onClick = {this.handleClick}`, but event will be passed instead. `onClick={ () => handleClick(item) }` here you can pass `item` manually – uladzimir Jun 27 '19 at 10:03
  • 1
    @uladzimir I think Accribus means to ask how we are using item in the arrow function's definition when it is not being passed as an argument? As far as i understand, here item is being resolved using lexical scoping. That is, item in the arrow function definition will be resolved to the item declared in render method because the arrow function is defined in the lexical environment of render method. Am i correct? – chetan May 24 '20 at 14:52
  • Thanks for this questions, but can you share how to write test case for ParentView component to pass handleClick(item) using jest ? – user9885720 Jun 07 '22 at 18:33
6

Accepted answer has a performance hit: ChildItem component will be re-rendered even if data hasn’t changed because each render allocates a new function (it is so because of .bind; same with arrow functions). In this particular case it is very easy to avoid such a problem by getting handler and its argument right from the props on new public class field:

export class ChildItem extends Component {

  onClick = () => {
    this.props.handleClick(this.props.item);
  }

  render() {
    return (
      <div>
        <a  onClick={this.onClick} />
      </div>
    );
  }
}

ParentView remains untouched.

dhilt
  • 18,707
  • 8
  • 70
  • 85
  • Does it make any difference if we use class or just function component in this case? – Leff Mar 12 '19 at 10:16
  • 1
    @Leff Sure. How you are going to define onClick in case of function component? It will be equal to defining it on render of class component, which we want to avoid. – dhilt Mar 13 '19 at 11:20
2

The ES6 way:

Using arrow functions =>

onClick={() => handleClick(item)}

(@havenchyk's answer is the ES5 way).

Snowman
  • 1,503
  • 1
  • 17
  • 39