1

I know that when using React class methods arrow functions are a great way to lock in this. For example I might have a click handler:

clickHandler = () => {
    // some code using 'this'
    // eg this.state
}

// ... and in the render method
<button onClick={() => this.clickHandler()}>Test Button</button>

However if I were to use a non-arrow function syntax what object owns the code? That is, what is this?

I have tried the following but Typescript will not compile:

clickHandler = function() {
    console.log(this);   // also tried to coerce this as any type  
}

// Error: 'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
cham
  • 8,666
  • 9
  • 48
  • 69

2 Answers2

1

Your "this" will be 'undefined' because React calls your function behind the scenes and it doesn't call it from the object instance (I.E MyComponent.onClickHandler(e)) it calls it like this (onClickHandler(e))

A simplified model of what's happening without React involved looks something like this.....

class FakeReactComponent {
  myName() {
     console.log(this);
  }
}
// this is what you give a onClick prop on an element.
const functionPassedToOnClick= new FakeReactComponent().myName;

console.log(
  functionPassedToOnClick() // this is how react calls it (this is undefined)
)

On method to get around this is to bind "this" of the function in the constructor of the component I.E

constructor(props: IProps) {
     super(props);
     this.onClickHandler = this.onClickHandler.bind(this);
}

This is actually the 'best practice' way to define all functions within a class for React Components as arrow functions don't go on the classes prototype and therefore fresh functions are created for every new React instance of that component; however with non-arrow functions each instance goes on the prototype and therefore reused for each react component instance.

Tricks like autobind are also not best practice because binding every function's this is unneccessary because only functions being called by React need to have their this bound and 99% of the time that's only functions being used as event handlers.

Shanon Jackson
  • 5,873
  • 1
  • 19
  • 39
  • Thanks for this. For anyone else who doesn't understand why 'undefined' appears here, as it was for me, then check this one: https://stackoverflow.com/questions/56918107/using-a-class-method-in-the-global-context-has-this-as-undefined?noredirect=1#comment100378952_56918107 – cham Jul 06 '19 at 23:41
1

It depends on how you call the function and whether or not you bind it.

class Test extends React.Component {
    clickHandler = function() {
        console.log(this);
    }

    render() {
        return (
            <div>
                <button onClick={this.clickHandler}>Option A</button>
                <button onClick={this.clickHandler.bind(this)}>Option B</button>
                <button onClick={() => this.clickHandler()}>Option C</button>
            </div>
        );
    }
}

With the Option A button, the click handler is passed directly to the <button /> element. Since React assumes you bind this and won't do it for you, the this inside clickHandler will be undefined.

With the Option B button we're explicitly binding this.clickHandler to this, in which case the this inside clickHandler will refer to that specific instance of the Test component.

For Option C (which is what you're doing in your question) we are not setting this.clickHandler as the onClick handler, but we're defining a new arrow function that calls this.clickHandler. In this case, the new arrow function will automatically get the Test component as this and that one is passed along to clickHandler.

Note that the Option A version will allow components to explicitly bind the handler:

class Button extends React.Component {
    render() {
        return (
            <button
                onClick={this.props.onClick.bind(this)}
                children={this.props.children}
            />
        );
    }
}

class Test extends React.Component {
    clickHandler = function() {
        console.log(this);
    }

    render() {
        return <Button onClick={this.clickHandler}>Click</Button>;
    }
}

In this case, this within Test.clickHandler() will actually be a reference to the Button instance.

rickdenhaan
  • 10,857
  • 28
  • 37