6

I may be wrong but my understanding is that this

var foo = {                                                                                                                                                           
    'bar': x => x + 1,                                                                                                                                                
    'baz': function(y){ return this.bar(y); }                                                                                                                         
};                                                                                                                                                                    

foo.baz(1);

should work fine since I took care not to define foo.baz as an arrow function and so this inside baz is equal to foo. Of course, when I test it on a console it works as I expected.

Now, I have a very similar setup with React in which this gets undefined for some reason. This is the code:

const MapRoom = {                                                                                                                                                     
    'getStyleFromCoords': coords => {                                                                                                                                 
        return {  // account for borders                                                                                                                              
            'left': coords[0] + 1,                                                                                                                              
            'top': coords[1] + 1,                                                                                                                               
            'width': (coords[2] - coords[0]) - 2,                                                                                                         
            'height':(props.coords[3] - props.coords[1]) - 2,                                                                                                         
        };                                                                                                                                                            
    },                                                                                                                                                                
    'Disabled': function(props){                                                                                                                                      
        console.log('this', this);  // undefined                                                                                                                                    
        const style = this.getStyleFromCoords(props.coords);  // error                                                                                                           

        return (                                                                                                                                                      
            <div                                                                                                                                                      
              className="room-selected"                                                                                                                               
              style={style}                                                                                                                                           
              title={props.number}                                                                                                                                    
              ></div>                                                                                                                                                 
        );                                                                                                                                                            
    }
}

and then

renderRooms(){                                                                                                                                                        
    // stuff                                                                                               

    return this.state.coords.map((coords, index) => {                                                                                                                 
        // more stuff
        if(disabled){                                                                                                                                                                 
            return (                                                                                                                                                                      
                <MapRoom.Disabled                                                                                                                                                           
                    key={roomNumber}                                                                                                                                    
                    number={roomNumber}                                                                                                                                                       
                    coords={coords}                                                                                                                                     
                   />                                                                                                                                                  
            );                                                                                                                                                        
        } else if(...){}
    });
}

render(){
    return (
        <stuff>
            {this.renderRooms()}
        </stuff>
    );
}                                                                                                                                                                                                                                                                               

I'd say they should be analagous, but it seems that's not the case. Of course this is not much problem since I can just move the function outside the object and then there's no need for this to reference it, but I'm curious to know what is actually happening since I'm unable to reproduce the error.

In case it matters, I'm transpiling the code with Babel and the output is

var MapRoom = {                                                                                                                                                       
    'getStyleFromCoords': function getStyleFromCoords(coords) {                                                                                                       
        return { // account for borders                                                                                                                               
            'left': coords[0] + 1,                                                                                                                              
            'top': coords[1] + 1,                                                                                                                               
            'width': coords[2] - coords[0] - 2,                                                                                                           
            'height': coords[3] - coords[1] - 2                                                                                                           
        };                                                                                                                                                            
    },                                                                                                                                                                
    'Disabled': function Disabled(props) {                                                                                                                            
        console.log('this', this);                                                                                                                                    
        var style = this.getStyleFromCoords(props.coords);                                                                                                                 

        return __WEBPACK_IMPORTED_MODULE_0_react___default.a.createElement('div', {                                                                                   
            className: 'room-selected',                                                                                                                               
            style: style,                                                                                                                                             
            title: props.number                                                                                                                                       
        });                                                                                                                                                           
    }
 }  

This is inside an anonymous function created by WebPack compiler.

cronos2
  • 288
  • 5
  • 13
  • 2
    You showed us how you were calling `foo.baz`, but not what is calling `MapRoom.Disabled`. When using a normal `function` it is how the function is _called_ that sets `this`, not where it was defined, so there is no way to answer this. – loganfsmyth Aug 12 '17 at 18:11
  • @loganfsmyth you're totally right, my bad. Let me know if it's enough now. – cronos2 Aug 12 '17 at 18:21
  • The compilation result doesn't use `this` anymore. – melpomene Aug 12 '17 at 18:28
  • is `MapRoom` inside a react component `class`? – Sagiv b.g Aug 12 '17 at 18:29
  • @Sag1v no, it was meant to be a "component collection" – cronos2 Aug 12 '17 at 18:33
  • @melpomene that's because I copied the compiled output of the version I'm using right now where the helper function is outside. Sorry about that, it's corrected now. – cronos2 Aug 12 '17 at 18:34
  • Using `MapRoom.Disabled` as a react component is not a method call. It's more like passing it around as a callback: [How to access the correct `this` / context inside a callback?](https://stackoverflow.com/q/20279484/1048572) – Bergi Aug 12 '17 at 18:40
  • Possible duplicate of [How to access the correct \`this\` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – Michael Freidgeim May 08 '19 at 09:09

1 Answers1

4

The this value in a normal function is set based on how the function is called. The structure of how the object is declared has no effect, it's just that foo.baz(1); sets this to foo.

To break it down,

foo.baz(1);

is equivalent to

let _tmp = foo;
_tmp.baz.call(_tmp, 1);

where in this case the _tmp could be pretty much skipped in place of just using foo.

In your JSX case

<MapRoom.Disabled />

is doing

declareComponent(MapRoom.Disabled);

where that function just gets the function, e.g.

function declareComponent(someFunc){
    someFunc();
}

By the time the function is called, it's just a function, there's no obj.someFunc that would cause this to be obj, so it ends up being undefined.

To clarify, the fact that your function is declared as a property on an object has no effect on anything, it is still just a function and it is up to you to ensure that the function is called with the proper this value if you have a certain expectations. For instance you could do

const MapRoomDisabled = MapRoom.Disabled.bind(MapRoom);

<MapRoomDisabled />

and since the function has been bound with an explicit context, it would work as you expect.

But

var obj = {
    prop: function(){}
};

is no different than

var obj = {
    prop: null
};
obj.prop = function(){};

or

var someFunc = function someFunc(){}

var obj = {
    prop: someFunc,
};
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251