0

I'm a very beginner on React, i'm trying to refactor a ES5 class to ES6 class by comparing with my app's courses file. Everything was runing ok until this line:

 if (scrolledToBottom) {
      this.querySearchResult();
 }

here is a error messagem on the console when the user scroll to the page's botton:

• Console's message:

TypeError: this.querySearchResult is not a function handleOnScroll

 if (scrolledToBottom) {
> 81 |      this.querySearchResult(); // This line results the error
  82 |    }
  83 |  }

• Complete Code:

  import React, { Component } from 'react';
  import ReactDOM from 'react-dom';
  import $ from 'jquery';

  export default class InfiniteData extends Component {
  constructor(props) {
    super(props);
    this.state = {data: ""};      
  }  

  getInitialState() {
    return ({data: [], requestSent: false});
  }

  componentDidMount() {
    window.addEventListener('scroll', this.handleOnScroll);

    this.initFakeData();
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleOnScroll);
  }

  initFakeData() {
    var data = this.createFakeData(this.state.data.length, 100);

    this.setState({data: data});
  }

  createFakeData(startKey, counter) {
    var i = 0;
    var data = [];
    for (i = 0; i < counter; i++) {
      var fakeData = (<div key={startKey+i} className="data-info">Fake Data {startKey+i}</div>);
      data.push(fakeData);
    }

    return data;
  }

  querySearchResult() {
    if (this.state.requestSent) {
      return;
    }

    // enumerate a slow query
    setTimeout(this.doQuery, 2000);

    this.setState({requestSent: true});
  }

  doQuery() {
    // use jQuery
    $.ajax({
      url: "#",
      data: null,
      method: "GET",
      success: function(data, textStatus, jqXHR) {
        var fakeData = this.createFakeData(this.state.data.length, 20);
        var newData = this.state.data.concat(fakeData);
        this.setState({data: newData, requestSent: false});
      }.bind(this),
      error: function(jqXHR, textStatus, errorThrown) {
        this.setState({requestSent: false});
      }.bind(this)
    });
  }  


  handleOnScroll() {
    // http://stackoverflow.com/questions/9439725/javascript-how-to-detect-if-browser-window-is-scrolled-to-bottom
    var scrollTop = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
    var scrollHeight = (document.documentElement && document.documentElement.scrollHeight) || document.body.scrollHeight;
    var clientHeight = document.documentElement.clientHeight || window.innerHeight;
    var scrolledToBottom = Math.ceil(scrollTop + clientHeight) >= scrollHeight;


    if (scrolledToBottom) {
      this.querySearchResult();
    }
  }

  render() {
    return (
      <div>
        <div className="data-container">
          {this.state.data}
        </div>
        {(() => {
          if (this.state.requestSent) {
            return(
              <div className="data-loading">
                <i className="fa fa-refresh fa-spin"></i>
              </div>
            );
          } else {
            return(
              <div className="data-loading"></div>
            );
          }
        })()}
      </div>
    );
  }
};
claudiopb
  • 1,056
  • 1
  • 13
  • 25

2 Answers2

2

You need to bind your querySearchResult function with the class in constructor like this

constructor(props) {
    super(props);
    this.state = {data: ""};      
    this.querySearchResult = this.querySearchResult.bind(this);
  } 

Alternatively you can also use arrow function like this instead of binding

querySearchResult = () => {
    if (this.state.requestSent) {
      return;
    }

    // enumerate a slow query
    setTimeout(this.doQuery, 2000);

    this.setState({requestSent: true});
  }
Prakash Sharma
  • 15,542
  • 6
  • 30
  • 37
  • Prakash I tried your suggestion and it still doesn't work – claudiopb Sep 06 '17 at 17:51
  • I believe you will have to bind `handleOnScroll` function too just like you bind `querySearchResult` – Prakash Sharma Sep 06 '17 at 17:58
  • yes, by transforming the QuerysearchResult to a arrow function there is appearing the same erro message. Then I did your second suggestion by doing the same thing with the handleOnScroll fucntion an there appears another one error message: TypeError: this.state is undefined doQuery/<.success< data: null, method: "GET", success: function(data, textStatus, jqXHR) { var fakeData = this.createFakeData(this.state.data.length, 20); <--error on this line var newData = this.state.data.concat(fakeData); this.setState({data: newData, requestSent: false}); }.bind(this), – claudiopb Sep 06 '17 at 18:11
  • To keep it short, you should bind (or use arrow function) all methods you define in class (except react's lifecycle methods like render etc). Because thats how they will be able to keep the scope of `this`. So convert the methods like `doQuery` `createFakeData` `initFakeData` `success` `error` etc to arrow functions too. – Prakash Sharma Sep 06 '17 at 18:27
0

You have to .bind(this) in your constructor() to make sure this is still referencing the React component. If you don't bind it then this becomes undefined like you're experiencing. You can read more about it on the React event handling docs.

So in your case you would need to add the following line to the contructor():

this.querySearchResult = this.querySearchResult.bind(this);

Another thing to note is if you're converting a large amount of components from React.createClass() to ES6 Classes then you should use the React class codemod to save you time and effort.

Luke Glazebrook
  • 580
  • 2
  • 14