27

I am aware that dynamic children of a component must have a unique key as the following (modified example from official docs):

render: function() {
  var results = this.props.results;
  return (
    {results.map(function(result) {
      return <ChildComponent type="text" key={result.id} changeCallback={this.props.callbackFn}/>;
    })}
  );
}

Considering that ChildComponent is another React component nested here, with a render method as bellow

render: function() {
  var results = this.props.results;
  return (
    <div className="something">
       <input type="text" onChange={this.props.changeCallback} />
    </div>
  );
}

is there any way to access the key when callbackFn(event) is called?

Yan Foto
  • 10,850
  • 6
  • 57
  • 88
  • Maybe it would help to add the same data as an id `id={result.id}`? This could easily be accessed by `event.target.id` – Schmalitz Jun 21 '18 at 15:29

3 Answers3

36

Although the first answer is correct this is considered as a bad practice since:

A bind call or arrow function in a JSX prop will create a brand new function on every single render. This is bad for performance, as it will result in the garbage collector being invoked way more than is necessary.

Better way:

var List = React.createClass({
  handleClick (id) {
    console.log('yaaay the item key was: ', id)
  }

  render() {
    return (
      <ul>
        {this.props.items.map(item =>
          <ListItem key={item.id} item={item} onItemClick={this.handleClick} />
        )}
      </ul>
    );
  }
});

var ListItem = React.createClass({
  render() {
    return (
      <li onClick={this._onClick}>
        ...
      </li>
    );
  },
  _onClick() {
    this.props.onItemClick(this.props.item.id);
  }
});

Source: https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

barczag
  • 741
  • 6
  • 10
  • Fair point :) I would've added it as a comment to the accepted answer though! – Yan Foto Aug 08 '16 at 16:25
  • Technically the selected answer is the right answer and your answer would be an improvement. – Yan Foto Aug 10 '16 at 08:46
  • 1
    `React.createClass` autobinds all functions, which means this answer is creating the same number of functions as doing `.bind` in the accepted answer. Regardless of moving this to a new component, you will have to bind any function used as a callback in order for it to have the correct context when called. – Ross Allen Jun 08 '17 at 17:41
  • @RossAllen if you're using es6 classes you can do that with `_onClick = () => { /* ... */ }` instead of `_onClick() { /*...*/ }` – redbmk Oct 04 '17 at 17:17
  • @redbmk True. I was only refuting the claim that this is a "Better way". This answer encapsulates `item` by creating a new class instance for each iteration of the loop. My answer only creates a new function on each iteration. Both answers are functional, but this one is not objectively "better"; that is misleading. The `item` or `item.id` must be captured by a new scope (a function) or a new class instance. When functions are partially applied to capture a variable, it's not "bad for performance"; it's necessary for the component to work as intended. – Ross Allen Oct 05 '17 at 18:06
  • Yeah I like the [bind operator syntax](https://github.com/tc39/proposal-bind-operator), which would work more the way you're proposing. I'd be curious to see the performance difference though between creating bound functions in render vs on class instantiation for a page full of elements that get re-rendered often (e.g. [the Sierpinski triangle demo](https://github.com/claudiopro/react-fiber-vs-stack-demo)) – redbmk Oct 06 '17 at 18:21
  • @RossAllen btw, not to pester you, but it looks like the docs have changed. They now mention that `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.` https://reactjs.org/docs/handling-events.html – redbmk Oct 06 '17 at 18:53
  • Thanks for pointing the re-rendering note. They do use my same example in "Passing Arguments to Event Handlers" below. It's worth measuring whether this actually causes performance problems in your app, which is why I am reluctant to label "Class"-ifying every case like this strictly "better": https://reactjs.org/docs/handling-events.html#passing-arguments-to-event-handlers – Ross Allen Oct 13 '17 at 01:09
22

Partially apply the function callback by using JavaScript's native bind. This is mentioned in React's "Communicate Between Components" doc:

callbackFn: function(key) {
  // key is "result.id"
  this.props.callbackFn(key);
},
render: function() {
  var results = this.props.results;
  return (
    <div>
      {results.map(function(result) {
        return (
          <ChildComponent type="text" key={result.id}
            changeCallback={this.callbackFn.bind(this, result.id)} />
        );
      }, this)}
    </div>
  );
}
Ross Allen
  • 43,772
  • 14
  • 97
  • 95
  • 9
    Why can't you access `key` directly in a better way? This feels like a gross workaround... – grammar Nov 12 '14 at 18:10
  • Why does this feel like a workaround? You want to keep track of `result.id`, and by binding that value on each iteration you know it will be what you expect when the function is called. – Ross Allen Feb 15 '15 at 23:14
  • 1
    it felt weird when I used it at first, but after working with React for a few months (my comment was initially left in Nov) it feels totally normal. You are correct. – grammar Feb 17 '15 at 14:55
  • Cool good to know it's working out for you. I didn't realize the date on your comment. – Ross Allen Feb 17 '15 at 17:03
  • @Breedly I agree, this won't work for DOM events where you need the event parameter - did you find a way? – Dominic Sep 23 '15 at 19:51
  • @DominicTobias It will still work for DOM events. The event will be the second parameter. `bind` returns a partially applied function that can be called with additional arguments. `callbackFn(id, event)` could be its signature. – Ross Allen Sep 23 '15 at 21:32
  • @ssorallen damn I didn't realise bind did that - good to know! – Dominic Sep 23 '15 at 21:37
1

We can access data with Data Attributes

// ChildComponent.js
render: function() {
  var results = this.props.results;
  return (
    <div className="something">
       <input 
          type="text" 
          onChange={this.props.changeCallback} 
          data-key={this.props.key} // "key" was passed as props to <ChildComponent .../>
       />
    </div>
  );
}

Now; in your changeCallback method; you can access this data:

// ParentComponent.js
(evt) => {
  console.log(evt.target.dataset.key);
}

Good Luck...

Aakash
  • 21,375
  • 7
  • 100
  • 81