1

I have a method that calls an addListener to data that does not exist yet until you've clicked on a button. However, as I try to set that data inside a state, I get the following error:

TypeError: this.setState is not a function

I've tried binding my dataHandler like this:

this.dataHandler = this.dataHandler.bind(this)

yet it still returns this error. What am I forgetting here?

constructor(props){
    super(props)
    this.state = {
        selActive: 4,
        loaded: false,
        mapInfo: ''
    }
    this.onScriptLoad = this.onScriptLoad.bind(this)
    this.dataHandler = this.dataHandler.bind(this)
}

dataHandler = (getJson) => {
    fetch(getJson)
        .then(response => response.json())
        .then(featureCollection => 
            dataLayer = map.data.addGeoJson(featureCollection)
        );
    map.data.addListener('mouseover', function(event) {
        map.data.revertStyle();
        map.data.overrideStyle(event.feature, {fillColor: 'blue'});
        // THIS IS WHERE I ADD MY SETSTATE. 
        // HOWEVER, THIS RETURNS AN ERROR ON HOVER
        this.setState({mapInfo: event.feature.countryNL})
    });
}
Cédric Bloem
  • 1,447
  • 3
  • 16
  • 34

3 Answers3

3

In JavaScript this always refers to the "owner" of the function we're executing, or rather, to the object that a function is a method of. Use arrow function instead. An arrow functions doesn't have its own this/super/arguments binding. It inherits them from its parent lexical scope.

map.data.addListener('mouseover', (event) => {
    map.data.revertStyle();
    map.data.overrideStyle(event.feature, {fillColor: 'blue'});
    // THIS IS WHERE I ADD MY SETSTATE. 
    // HOWEVER, THIS RETURNS AN ERROR ON HOVER
    this.setState({mapInfo: event.feature.countryNL})
});

Reference:

Hai Pham
  • 2,188
  • 11
  • 20
  • Well, not exactly. `this` refers to the context of the function. If there is no context, then `this` presents a global object, yes, but if you are using function as event listener, for example, then `this` will reffer to the `currentTarget` of the event. – Limbo Jan 23 '19 at 08:29
  • 1
    @LevitatorImbalance thanks for the information, I have updated my answer – Hai Pham Jan 23 '19 at 08:43
1

this inside a normal function either refers to the global object or the current instance (in case of being invoked via new). However this inside an arrow function is captured from the outer scope. So this inside that addListener callback refers to the global object (window). To solve this problem you have the following options:

  • Using arrow function:

    map.data.addListener('mouseover', (event) => {
        map.data.revertStyle();
        map.data.overrideStyle(event.feature, {fillColor: 'blue'});
        // THIS IS WHERE I ADD MY SETSTATE. 
        // HOWEVER, THIS RETURNS AN ERROR ON HOVER
       this.setState({mapInfo: event.feature.countryNL})
    });
    
  • Using bound function:

    map.data.addListener('mouseover', function (event) {
        map.data.revertStyle();
        map.data.overrideStyle(event.feature, {fillColor: 'blue'});
        // THIS IS WHERE I ADD MY SETSTATE. 
        // HOWEVER, THIS RETURNS AN ERROR ON HOVER
       this.setState({mapInfo: event.feature.countryNL})
    }.bind(this));
    
frogatto
  • 28,539
  • 11
  • 83
  • 129
1

First of all, I do not recommend you to use anonymous functions to be set as event listeners, because in this case you will not be able to removeEventListener or something simillar (probably, because it is not clear, what is the map here).

The second thing is that you should not bind your dataHandler, because it is already an arrow function, so there is no problems with this inside the dataHandler context. The thing that you should bind is your manual event listerer. So the result code should look like this:

constructor(props){
    super(props)
    this.state = {
        selActive: 4,
        loaded: false,
        mapInfo: ''
    }
    this.onScriptLoad = this.onScriptLoad.bind(this)
    this.dataHandler = this.dataHandler.bind(this)
    this.handleMouseOver = this.handleMouseOver.bind(this)
}

dataHandler = (getJson) => {
    fetch(getJson)
        .then(response => response.json())
        .then(featureCollection => 
            dataLayer = map.data.addGeoJson(featureCollection)
        );
    map.data.addListener('mouseover', this.handleMouseOver);
}

handleMouseOver(event) {
    map.data.revertStyle();
    map.data.overrideStyle(event.feature, {fillColor: 'blue'});
    this.setState({mapInfo: event.feature.countryNL})
}

or, using an arrow function, that is automatically being binded:

constructor(props){
    super(props)
    this.state = {
        selActive: 4,
        loaded: false,
        mapInfo: ''
    }
    this.onScriptLoad = this.onScriptLoad.bind(this)
    this.dataHandler = this.dataHandler.bind(this)
}

dataHandler = (getJson) => {
    fetch(getJson)
        .then(response => response.json())
        .then(featureCollection => 
            dataLayer = map.data.addGeoJson(featureCollection)
        );
    map.data.addListener('mouseover', this.handleMouseOver);
}

handleMouseOver = (event) => {
    map.data.revertStyle();
    map.data.overrideStyle(event.feature, {fillColor: 'blue'});
    this.setState({mapInfo: event.feature.countryNL})
}

Hope, this will help you! :)

Limbo
  • 2,123
  • 21
  • 41