0

This problem caused me a headache. this keyword when you set a state is not accessible inside the ajax call.

I think i miss-understand something in how react components work.

How should i use this keyword in this specific situation as provided below? and in general if this is not accessible.

class PollsList extends Component {
    constructor() {
        super();

        this.state = {
            polls: []
        }
    }
    componentDidMount() {
        $.ajax({
            url: "/mypolls",
            type: 'get',
            dataType: 'json',
            contentType: 'application/json',
            success: function (polls_list) {
                console.log(polls_list);

                for (let i = 0; i < polls_list.length; i++) {
                    let data = polls_list[i].poll[0];
                     this.setState(()=>{polls:data});
                }
            },
            error: function (err, status, xhr) {
                console.log(err);
            }
        });
    }
    render() {
        return (
            <Fragment>
                <div class='container'>
                    <ul id='polls_list'>{this.state.polls}</ul>
                </div>
            </Fragment>
        )
    }
}

EDIT: PROBLEM SOLVED!

Thanks for everyone contributed.

class PollsList extends Component {
    constructor() {
        super();
        this.state = {
            polls: []
        }

        this.fetchPolls = this.fetchPolls.bind(this);
    }
    componentDidMount() {
        $.ajax({
            url: "/mypolls",
            type: 'get',
            dataType: 'json',
            contentType: 'application/json',
            success: (polls_list)=>this.fetchPolls(polls_list), // this must look like that.
            error: (err, status, xhr) =>{
                console.log(err);
            }
        });
    }

    fetchPolls(polls_list){
        for (let i = 0; i < polls_list.length; i++) {
            let data = polls_list[i].poll[0];
            this.setState({polls:data});
        }
    }
    render() {
        console.log(this.state.polls);
        return (
            <Fragment>
                <div className='container'>
                    <ul id='polls_list'>{this.state.polls.option1} {this.state.polls.option2}</ul>
                </div>
            </Fragment>
        )
    }
}
ANUBIS
  • 666
  • 1
  • 9
  • 20
  • Turn `success: function (polls_list) {` into `success: (polls_list) => {`. Read up on arrow functions – CertainPerformance May 23 '18 at 04:45
  • Its an old code. i certainly know about arrow functions. – ANUBIS May 23 '18 at 04:46
  • The `success` callback function has its own value for `this` separate from your `class`. To control the value, you can either [use `$.ajax()`'s `context` setting](https://stackoverflow.com/questions/6394812/this-inside-of-ajax-success-not-working) or use the options discussed in: [How to access the correct `this` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – Jonathan Lonowski May 23 '18 at 04:49
  • [javascript - How does the "this" keyword work?](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – Andreas May 23 '18 at 04:52

4 Answers4

2

In this scenario, your this is in the context of this function instead of the component class:

function (polls_list) {
  for (let i = 0; i < polls_list.length; i++) {
    let data = polls_list[i].poll[0];
    this.setState(()=>{polls:data});
  }
}

Better practice to deal with this kind of situation is binding this which still in the context of the component in the component constructor:

class PollsList extends Component {
  constructor() {
    super();

    this.state = {
        polls: []
    }

    this.successHandler = this.successHandler.bind(this);
  }

  componentDidMount() {
    $.ajax({
        url: "/mypolls",
        type: 'get',
        dataType: 'json',
        contentType: 'application/json',
        success: this.successHandler(polls_list),
        error: function (err, status, xhr) {
            console.log(err);
        }
    });
  }

  successHandler(polls_list) {
    for (let i = 0; i < polls_list.length; i++) {
      let data = polls_list[i].poll[0];
      this.setState(()=>{polls:data});
    }
  }

  render() {
    ...     
  }
}
Carr
  • 2,691
  • 1
  • 19
  • 27
  • `this.successHandler = this.successHandler.bind(this);` I've seen this line a lot but no idea what it actually does? – ANUBIS May 23 '18 at 04:56
  • @SharlSherif, I've added more explanation, we use `bind` method to pass the `this` which refers to the component to the ajax handling function. – Carr May 23 '18 at 05:03
  • I got it. Thank you. – ANUBIS May 23 '18 at 05:07
2

Bind this to the success handler in the constructor. This should solve the scope of this object

constructor (args) {
   super(...args);
   /* some state variables */

   this.successHandler = this.successHandler.bind(this);
}

function successHandler(polls_list) {
  for (let i = 0; i < polls_list.length; i++) {
     let data = polls_list[i].poll[0];
        this.setState(()=>{polls:data});
   }
}

function (err, status, xhr) {
   console.log(err);
}

componentDidMount() {
  $.ajax({
    url: "/mypolls",
    type: 'get',
    dataType: 'json',
    contentType: 'application/json',
    success: successHandler,
    error: errorHandler
  });
}
1

A quick fix would be to just

const var that = this

inside componentDidMount and then use that.setState

What this would do is save the context of this in the "that" variable for use later. or better do this

success: function (polls_list) {
                console.log(polls_list);

                for (let i = 0; i < polls_list.length; i++) {
                    let data = polls_list[i].poll[0];
                     this.setState(()=>{polls:data});
                }
            }.bind(this),

this will bind the context to the success function

supra28
  • 1,646
  • 10
  • 17
0

I think the cleanest way to resolve this issue is using fat arrow functions (which automatically bind to the component). So your component would look like this:

class PollsList extends Component {
    // You can also set default state if your webpack can handle it
    state = {
        polls: []
    }
    componentDidMount() {
        $.ajax({
            url: "/mypolls",
            type: 'get',
            success: this.successHandler,
        });
    }
    // Fat arrow functions auto binds to this to the Class
    successHandler = response => { 
        // handle the response
    } 
    render() {
        return (
            <Fragment>...</Fragment>
        )
    }
}
Juan Solano
  • 1,188
  • 1
  • 17
  • 32