Anton Telesh
Anton Telesh

Reputation: 3822

React: how to pass arguments to the callback

I have a list of elements inside my react component, and I want them to be clickable. On click I call some external function passing item ID in arguments:

render () {
  return (
    <ul>
      {this.props.items.map(item => (
        <li key={item.id} onClick={() => {doSomething(item.id)}></li>
      ))}
    </ul>
  )
}

This code works, but it has a big performance drawback: a lot of new anonymous functions are being created on each call to render.

How can I pass that doSomething function as a reference here while still being able to provide a item.id to it?

Upvotes: 13

Views: 21549

Answers (3)

Pibiche
Pibiche

Reputation: 181

You could use data-attributes, to set the correct id on each item while using the same function:

function doSomethingFromEvent(event){
  return doSomething(event.target.dataset.id);
}

render () {
  return (
    <ul>
      {this.props.items.map(item => (
        <li key={item.id} data-id={item.id} onClick={doSomethingFromEvent}></li>
      ))}
    </ul>
  )
}

When setting data-* attributes in your element, you can get it back with dataset, in the form of a hash. For example, in doSomethingFromEvent I have event.target.dataset = {id: *id*}. See more on MDN

This is even cleaner when updating a hash (the state for example), with <li key={item.id} data-myattriute={myvalue} onClick={this.handleClick}></li>, I can simply define handleClick such as:

handleClick(event){
    // Here event.target.dataset = {myattribute: myvalue}

    Object.assign(myObject, event.target.dataset);
    // or
    this.setState(event.target.dataset);
}

Coming back to your problem, the great thing with this approach is that if you ensure your container element (ul) cannot be clicked outside its children with data-attributes (li), which is your case, you can declare the function on it:

render () {
  return (
    <ul onClick={doSomethingFromEvent}>
      {this.props.items.map(item => (
        <li key={item.id} data-id={item.id}></li>
      ))}
    </ul>
  )
}

Now your function is created a single time, and is not even repeated in each item.

Upvotes: 17

Tholle
Tholle

Reputation: 112787

You could create a new component for every item in the array and use the props, like this:

class Li extends React.Component {
  render() {
    return <li onClick={this.onClick}> {this.props.children} </li>;
  }
  onClick = () => {
    console.log(this.props.item);
  };
}

class App extends React.Component {
  state = {
    items: [
      {id: 1, name: 'one'},
      {id: 2, name: 'two'},
      {id: 3, name: 'three'},
    ]
  };
  render() {
    return <ul> 
      {this.state.items.map(i => 
        <Li key={i.id} item={i}>{i.name}</Li>
      )} 
    </ul>;
  }
}

Upvotes: 1

Tim Roberts
Tim Roberts

Reputation: 1170

What you can do is create a partially applied or higher order function to enclose the item.id and pass it along. So let's look at a toy example of this:

class App {

   partiallyApplied = id => e => {
     console.log(id,'this is passed in first')
     console.log(e,'this is passed in second')
   }

   render(){
     return (
       <button onClick={this.partiallyApplied(1234)}>Click Me</button>
     )
   }

}

Now you have access to 1234 along with your event object

This is use transform-class-properties babel plugin. If do not or cannot use that, you can probably do something like this:

partiallyApplied(id){
  return function(e){
   console.log(id,'this is id')
   console.log(e,'this is event')
  }
}

but then you will have to bind this during your call and I just don't like that everywhere.

Upvotes: 2

Related Questions