0

I was working off a react drag and drop tutorial that was done for ES5, but I'm working with ES6.

Here's my code:

  class Song extends React.Component{
    constructor(props){
      super(props);
      this.state = {isPlaying:false,dragMode:editMode == 1 ? true : false,eligibleToDrag: false};
      //this.dragStart = this.dragStart.bind(this);
    }

    _clickAction(){

      if(this.state.dragMode == false){
        changeSong(this.props.arrIndex); 
        playSong();
      }
    }

    dragStart(e){
      Array.prototype.forEach.call(document.getElementById('screen2').getElementsByClassName('track'),function(e1){
        if(e1.getAttribute('data-songid') == e.currentTarget.getAttribute('data-songid')){
          this.setState({eligibleToDrag: false});
          console.log('ELIGIBLE?',this.state.eligibleToDrag);

        }
        else{
          this.setState({eligibleToDrag: true});
          console.log('ELIGIBLE?',this.state.eligibleToDrag);
        }
      });
      console.log(this.state.eligibleToDrag);
      if(e.currentTarget.parentElement.parentElement.id=="screen"){
        e.dataTransfer.effectAllowed = 'link';
      }
      if(e.currentTarget.parentElement.parentElement.id=="screen2"){
        e.dataTransfer.effectAllowed = 'move';
      }
      draggedObject = e.currentTarget;
      e.dataTransfer.setData("text/html",e.currentTarget);
    }

    dragEnd(e){

    }

    dragOver(e){
      e.preventDefault();
      var relY = e.clientY - e.target.offsetTop;
      var height = e.target.offsetHeight / 2;
      if(e.target.parentNode.parentNode.id == 'screen2'){
        if(relY > height){
          e.target.parentNode.insertBefore(draggedObject,e.target.nextElementSibling);
        }
        else if(relY < height){
          e.target.parentNode.insertBefore(draggedObject,e.target);
        }
      }
      if(e.target.parentNode.parentNode.id == 'screen' && draggedObject.parentNode && draggedObject.parentNode.parentNode.id == 'screen2'){
        draggedObject.remove();
        //e.stopPropagation();
      }
      return;

    }
    render(){
      var isDragMode = this.state.dragMode == true;
      if(isDragMode){
        document.getElementById('screen').classList.add("drag");
        document.getElementById('screen2').classList.add("drag");
      }

      return <div data-songid={this.props.trackInfo.Id} onClick={this._clickAction.bind(this)} className={"track"} draggable={isDragMode ?  "true": "false"} onDragEnd={isDragMode ? this.dragEnd.bind(this) : ''} onDragOver={isDragMode ? this.dragOver.bind(this) : ''} onDragStart={isDragMode ? this.dragStart.bind(this) : ''}>
        <span draggable={"false"}>{this.props.trackInfo.Title}</span><br draggable={"false"} />
        <span draggable={"false"}><i draggable={"false"}>{this.props.trackInfo.Artist}</i></span>
      </div>
    }

  }

The class works until I add that Array.prototype.forEach.call section on dragStart(e). It then starts giving me "Cannot read property 'setState' of undefined" in the console, pointing to that section. I'm not sure what I'm missing because to my knowledge I've binded everything in the render method. My guess is that I haven't binded everything, but I'm not sure what else to do.

Josh
  • 13
  • 2
  • 4
  • don't use _this_ in an [] method without passing what _this_ should be as a 2nd argument. so, in dragstart, replace your first `});` after the `if/else` with `}, this);` and retry – dandavis Jan 10 '16 at 21:26

1 Answers1

2

The reason is that in your forEach call, the variable this no longer refers to what you expect it to be. If you insist on doing it the current way you should declare the initial this variable up higher and reference it in your forEach function:

...
dragStart(e){
  var context = this;
  Array.prototype.forEach.call(document.getElementById('screen2').getElementsByClassName('track'),function(e1){
    if(e1.getAttribute('data-songid') == e.currentTarget.getAttribute('data-songid')){
      context.setState({eligibleToDrag: false}); //change this -> context
      console.log('ELIGIBLE?',context.state.eligibleToDrag);

    }
    else{
      context.setState({eligibleToDrag: true});
      console.log('ELIGIBLE?',context.state.eligibleToDrag);
    }
  });
...

However since you are using ES6 you can make use of Shiny,New,Fat Arrow Function.

...
dragStart(e) {
  Array.prototype.forEach.call(document.getElementById('screen2').getElementsByClassName('track'), e1 => {
    //use `this` as per usual
  }
}

The above is possible because ES6 Arrow functions capture the this value of the enclosing context

EDIT

Adding on from what dandavis mentions, you could actually set the value of this in the callback function of forEach. It's the last argument of the forEach function. So if OP wants to fix this the quickest way possible he can just append this to the last parameter of hisforEach call and it'll work like magic.

Array.prototype.forEach.call(document.getElementById('screen2').getElementsByClassName('track'), function(e1){
  //As per usual
},this);
Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
jkris
  • 5,851
  • 1
  • 22
  • 30
  • Also look up "Lexical this". Here's a link to another SO post on that. http://stackoverflow.com/questions/1047454/what-is-lexical-scope – jkris Jan 10 '16 at 21:46
  • 1
    forEach allows run-time setting of this, so there's no need to introduce impure closures and extra variables: you can more easily make _this_ mean _this_. of course fat arrows outmod the problem, but they can be hard to use on complex multi-line callbacks... – dandavis Jan 10 '16 at 22:00