Ad

ES6 With React, Can't SetState - 'Cannot Read Property 'setState' Of Undefined'?

- 1 answer

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.

Ad

Answer

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(doc
Array.prototype.forEach.call(document.getElementById('screen2').getElementsByClassName('track'), function(e1){
  //As per usual
},this);
Ad
source: stackoverflow.com
Ad