Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

reactjs

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')
    );
like image 733
Adam L Avatar asked Jan 22 '16 02:01

Adam L


2 Answers

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);
like image 122
Matthew Herbst Avatar answered Oct 23 '22 03:10

Matthew Herbst


You should add this to .map(), because inside of .map() this refers to window or undefined when in strict mode.

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);
 ! here !
like image 27
Alex Pivtorak Avatar answered Oct 23 '22 05:10

Alex Pivtorak