Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 711
Girafa Avatar asked Jan 23 '17 13:01

Girafa


2 Answers

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.

like image 179
Pibiche Avatar answered Sep 29 '22 10:09

Pibiche


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.

like image 21
Tim Roberts Avatar answered Sep 29 '22 10:09

Tim Roberts