1

I am mapping over an array of objects that looks like this

events = [
   {
     id: 1,
     name: 'Real vs Barcelona'
   },
   {
     id: 2,
     name: 'Celtic vs Rangers'
   }
];

I then want to pass the id to a function that updates the relevant event. This work, I am using an arrow function to achieve this.

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

render() {

    return (
      <div>
         {this.props.events.map(event => {
           return <p key={event.id} onClick={() => this.props.updateEvent(event.id)}>{ event.name }</p>
         });
      </div> 
    )
}

I have read there are performance implications of doing it this way, and also my es-linter is showing an error. Does anyone know another way I can approach this and pass the id in a different way.I don't think I want to use the ES5 bind approach either.

Maybe something like this

render() {

let updateEvent = (e) => {
  //This obviously won't work but something similar? 
  this.props.updateEvent(e.target);
}

return (
  <div>
     {this.props.events.map(event => {
       return <p key={event.id} onClick={updateEvent}>{ event.name }</p>
     });
  </div> 
)

}

  • If you don't want to use arrow function you could do `onClick={function() { this.props.updateEvent(event.id) } }` – abdul Sep 28 '17 at 15:16
  • It only matters if it is a performance issue.. It will create a new function on every render for each item.. You could on ComponentWillReceiveProps create a map of functions and store it on the component, and then in the loop say `this.eventFuncs[event.id]` but I have never seen it as a perf issue.. – TryingToImprove Sep 28 '17 at 15:28

3 Answers3

4

If the value you want to pass is serializable then you can assign it as an attribute as Chris mentions in his answer.

The more generic solution is to create a new component and pass the data as props:

class Event extends React.Component {
  _onClick = () => this.props.onClick(this.props.event.id);

  render() {
    return (
      <p onClick={this._onClick}>{ this.props.event.name }</p>
    );
  }
}

And in the caller:

<div>
   {this.props.events.map(event => {
     return <Event key={event.id} event={event} onClick={this.props.updateEvent} />
   });
</div> 
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 1
    Ah, you beat me to it. Was gonna add this option to my post ;) – Chris Sep 28 '17 at 15:26
  • There is a runnable example of something similar to the above that I made not long ago, here: https://stackoverflow.com/a/46192328/2030321 – Chris Sep 28 '17 at 15:28
  • @FelixKling thanks for the answer. I can accept yours or Chris' but what I was thinking of was something like my answer below. Can you comment on the approach in that answer, and whether it is better/the same/worse than other solutions –  Sep 28 '17 at 15:31
  • @RaulRodriguez your approach will have the same performance impact which you were talking about in your initial approach, as it will recreate the function in every render, although the performance issue is very minor. Personally, I would go with Felix's approach, as Chris's solution is a bit difficult to extend, plus you have to add a data-* attribute to make it work. – Abhishek Jain Sep 28 '17 at 15:36
0

Since events pass the related DOM element you clicked on, you could read any attribute the element has. As such, you could do something similar to what you suggested at the end of your post:

class MyApp extends React.Component {

  render() {
    let updateEvent = (e) => {
      console.log(e.target.dataset.name);
    }
    
    return (
      <div>
         {[{id:0, name:"foo"}, {id:1, name:"bar"}].map(event => {
           return <p key={event.id} data-name={event.name} onClick={updateEvent}>{ event.name }</p>
         })}
      </div> 
    );
  }
}
 
ReactDOM.render(<MyApp />, document.getElementById("app"));
<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="app"></div>
Chris
  • 57,622
  • 19
  • 111
  • 137
  • 1
    I don't think `e.target.name` will work. `

    ` elements do not have such a property. You probably want to use a `data-*` attribute.

    – Felix Kling Sep 28 '17 at 15:17
  • Please don't use this approach. It will do what you ask, but you will have a hard time extending your "event" object, since you will need to update the DOM also. – TryingToImprove Sep 28 '17 at 15:30
  • @TryingToImprove, what do you mean by "extending the event"? – Chris Sep 28 '17 at 15:31
  • @Chris thanks for the answer. I can accept yours or Felix's but what I was thinking of was something like my answer below. Can you comment on the approach in that answer, and whether it is better/the same/worse than other solutions –  Sep 28 '17 at 15:32
  • @Chris I am thinking about adding new values/properties to the object. Then it needs to update the DOM also.. – TryingToImprove Sep 29 '17 at 09:21
0

I actually figured out what I think I was looking for.

render() {   

    return (
      <div>
         {this.props.events.map(event => {

            let updateEventHelper = () => {
               this.props.updateEvent(event.id);
            };

           return <p key={event.id} onClick={updateEventHelper}>{ event.name }</p>
         });
      </div> 
    )
}