5

I don't understand what's going on

componentDidMount() {
    console.log('componentDidMount');
    //const self = this; 
    let _id = this.props.match.params.id.toUpperCase();

    if (_id != this.state.id.toUpperCase()) {

        axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
            .then(response => {
                // let _currentcoin = { ...resp.data.RAW.BTC.USD, ticker: _id };
                this.setState({ id: _id }); //this == undefined
            });
    }
}

I can get a response back but this is always undefined and I'm unable to setState. I'm using an arrow function which I thought was scope 'this' to the component level. I can fix it by making a new var and setting 'this' before I make the request. I know that this should be working though. What am I missing?

My entire component

import React, { Component } from 'react';
import axios from '../../axios';


class CoinViewer extends Component {

state = {
    coin: {},
    hasLoaded: false,
    id: ''
}

componentDidMount() {
    console.log('componentDidMount');
    //const self = this; 
    let _id = this.props.match.params.id.toUpperCase();

    if (_id != this.state.id.toUpperCase()) {

        axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
            .then( resp => {
                // let _currentcoin = { ...resp.data.RAW.BTC.USD, ticker: _id };
                this.setState({ id: _id });
            });
    }
}

componentWillMount() {

}

componentWillUpdate() {


}

componentDidUpdate() {

}

getCompleteCoinData(_id) {

}


render() {


    return (
        <div>
            CoinViewer Component: {this.state.id} sads
        </div>
    )
}

}

export default CoinViewer

  • 1
    I am using the same approach, but with fetch. It seems to be working !! – Ragul Parani Jun 07 '18 at 18:01
  • 1
    You should be able to use the arrow function to access the `this` context of your component. See here: https://stackoverflow.com/questions/41194866/how-to-set-state-of-response-from-axios-in-react Could you share the code for your whole component? – SlimPDX Jun 07 '18 at 18:11
  • 2
    not able to reproduce your issue, I tried something similar to your code, but it worked for me. https://codepen.io/smilesaayush/pen/VdKoJa If I don't use arrow function after axios response then it gives error, but otherwise it works. – Ronn Wilder Jun 07 '18 at 18:22
  • If I declare let self == this and then self.setState(...) it works. I never had to do that for any other axiom promises. It's a bit confusing for me – Kai CriticallyAcclaimed Cooper Jun 07 '18 at 18:47
  • 1
    maybe try logging what `this` is in every step to see if its what you expect. also verify you are able to use arrow functions in other places in your code so you know its not a transpiler error – John Ruddell Jun 07 '18 at 19:00
  • *Unless I'm missing something*, you either (1) have some non-standard enigmatic manually-driven garbage collection going on, which somehow manages to delete the component instance before the `then` callback runs, (2) have a buggy transpiler, or (3) simply did not copy the original code to your question ([Occam's razor principle](https://simple.wikipedia.org/wiki/Occam%27s_razor) applies). The code in your question *should* work. – John Weisz Jun 07 '18 at 20:02
  • i think the error message for this line `this.setState({ id: _id }); //this == undefined` is actually undefined is not a function – Eric Hasselbring Jun 07 '18 at 20:20

3 Answers3

4

Solution 1: arrow functions..

requestSuccess = (resp) => {
  // let _currentcoin = { ...resp.data.RAW.BTC.USD, ticker: _id };
  this.setState({ id: _id });
}

componentDidMount() {
  console.log('componentDidMount');
  //const self = this; 
  let _id = this.props.match.params.id.toUpperCase();
  if (_id != this.state.id.toUpperCase()) {
     axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
       .then(this.requestSuccess);
  }
}

Solution 2: binding

componentDidMount() {
  console.log('componentDidMount');
  //const self = this; 
  let _id = this.props.match.params.id.toUpperCase();
  if (_id != this.state.id.toUpperCase()) {
     axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
       .then((resp) => {
          // let _currentcoin = { ...resp.data.RAW.BTC.USD, ticker: _id };
          this.setState({ id: _id });
     }.bind(this));
  }
}
Medet Tleukabiluly
  • 11,662
  • 3
  • 34
  • 69
3

:Edit Wow, the below is kinda true, but the real issue is you didn't initialize state. https://reactjs.org/docs/react-component.html#constructor

constructor() {
  super();
  this.state = {
    coin: {},
    hasLoaded: false,
    id: ''
  }
}

You could use lexical scoping and fix like this, this is a popular pattern to protect this.

Basically, when you use promises or functions from other libraries/ APIs you do not know what they have set their context inside the callback functions to.

In order to use the context you want, you keep the context you need saved in a variable within scope and reference it there _this, rather than by pointing to the context this. I'd recommend reading 'you dont know js' to understand this concept further.

componentDidMount() {
  console.log('componentDidMount');
  const _this = this; 
  let _id = _this.props.match.params.id.toUpperCase();

  if ( _id != _this.state.id.toUpperCase() ) {
    axios.get('/data/pricemultifull?fsyms=' + _id + '&tsyms=USD')
      .then(response => {
        _this.setState({ id: _id }); //this == undefined
      });
  }
}
Eric Hasselbring
  • 1,374
  • 1
  • 10
  • 18
  • 1
    I would add that `this` is not a variable. Closure does not apply on it. Also, I prefer `const that = this` :-) – RaphaMex Jun 07 '18 at 19:56
  • 2
    Arrow functions explicitly preserve the lexical scope of `this` by definition, so inside the callback function passed to `then`, `_this === this` _should_ be true. The problem is coming from somewhere else (a bad transpiler perhaps?). – John Weisz Jun 07 '18 at 19:59
  • @JohnWeisz: I would expect `this` to be at least `axios`. Why do you think it should work? – RaphaMex Jun 07 '18 at 20:25
  • @RaphaMex it is because its creating an anonymous function in the parameters field, thus having a lexical scope as `componentDidMount` – Eric Hasselbring Jun 07 '18 at 20:28
  • This is what I went with and it worked. It just seem the declaration of _this was extraneous const _this = this; _this.setState({ id: _id }) – Kai CriticallyAcclaimed Cooper Jun 08 '18 at 12:35
3

When working with React.js, chances are you have faced a problem how to access this from inside the promise.There is more than one solution to resolve this reference inside the promise. The old approach would be setting the self = this reference While this would work, the recommended solution, which is more inline with ES6, would be to use an arrow function here:

class Component extends React.Component { 
  componentDidMount() {
    let component = this;
    axios.get('http://…').then(function(data) {
      component.setState( { name: data.blah } );
    });
  }
}

The arrow syntax, as stated above, is a much smarter way to allow use of this to make reference to React.Component classes, as we can see below:

class Component extends React.Component { 
  componentDidMount() {
    axios.get('http://…').then(data => {
      this.setState( { name: data.blah } );
    });
  }
}

Please note, instead of using function(data) { //body }, we used data => { //body }, and in this case this reference won’t get the promise instance back.

Saurabh
  • 774
  • 5
  • 15