Adam L Adam L - 2 months ago 44
React JSX Question

React onClick inside .map then change data in another sibling component

Day 1 with React and I'm not seeing a way to get the < button> onClick={this.props.handleClickPlay}>Play < /button> to play audio. If I move it under {audioListNodes} the button works fine. I'd like to have each link play a separate audio file eventually but for right now just playing the same file is a win, but moving the event handler into the list kills it. I'm assuming it's because THIS is no longer referencing AudioList but rather var data? Once I have the button firing how do I ID which button was clicked and change the AudioObject source?

var data = [
{voice: "Drew", file:"Drew.mp3", volume: 90},
{voice: "Chris", file:"Chris.mp3", volume: 85},
{voice: "Patrick", file:"Patrick.mp3", volume: 85},
{voice: "Everett", file:"Everett.mp3", volume: 60},
];

var AudioList = React.createClass({
render: function() {
var audioListNodes = this.props.data.map(function(el, index) {
return (
<div author={el.voice}>
{el.file}
<button onClick={this.props.handleClickPlay}>Play</button>
</div>
);
});
return (
<div className="audioList">
{audioListNodes}
</div>
);
}
});
var AudioObject = React.createClass({
play: function () {
var audio = this.getDOMNode();
audio.load();
audio.play();
},
render: function() {
return (
<audio preload="auto" controls="true" autoPlay="">
<source src='female/DeDe Splaingard.mp3'></source>
Your browser does not support audio.
</audio>
);
}
});
var App = React.createClass({
handleClickPlay: function() {
this.refs.audioObject.play()
},
render: function() {
return (
<div>
<AudioObject ref="audioObject" />
<AudioList data={this.props.data} handleClickPlay={this.handleClickPlay} />
</div>
);
}
});
ReactDOM.render(
<App data={data} />,
document.getElementById('content')
);

Answer

You have to bind(this) to the anonymous function since the context inside of .map changes:

var audioListNodes = this.props.data.map(function(el, index) {
  return (
    <div author={el.voice}>
      {el.file}
      <button onClick={this.props.handleClickPlay}>Play</button>
    </div>
  );
}.bind(this));

Another option is to start using ES6 arrow functions, which lexically pass this:

var audioListNodes = this.props.data.map((el, index) => {
  return (
    <div author={el.voice}>
      {el.file}
      <button onClick={this.props.handleClickPlay}>Play</button>
    </div>
  );
});

As @Henrik Andersson mentioned in the comments, you can also pass this directly to map.

var audioListNodes = this.props.data.map(function(el, index) {
  return (
    <div author={el.voice}>
      {el.file}
      <button onClick={this.props.handleClickPlay}>Play</button>
    </div>
  );
}, this);
Comments