Preact: Cannot set property with ref callback

Created on 15 May 2016  Â·  5Comments  Â·  Source: preactjs/preact

I am trying to set a ref attribute using a class method:

export default class Foo extends Component {
    handleClick (event) {
      console.log(this.foobar);
    }

    setRef (ref) { // Using this syntax works in the REPL: setRef = (ref) => {  
      console.log(ref); // <input>
      console.log(this); // undefined
      this.foobar = ref; // error
      return ref;
    }

    render() {
        return (
            <div>
                <input ref={this.setRef} />
                <button onClick={this.handleClick}>click me</button>
            </div>
        );
    }
}

As already commented in the code snippet, this is undefined in setRef() and thus, setting this.foobar fails with a _Cannot set property 'foobar' of undefined_ error…

What am I missing here?

_Note: I am using ECMAScript 6 via Babel. In the Preact REPL I can use the alternative syntax as commented (fat arrow) to make the code working. Could this be a Babel issue?_

question

Most helpful comment

Hi @p3k: the reason setRef errors here is because without the fat arrow syntax, the method is not bound to the class instance. When preact calls your setRef function, it does not invoke it with context, so the value of this is always undefined. Using the fat arrow syntax or a locally defined function provides the desired value for this.

If you're getting an error when trying to transpile the fat arrow method syntax in Babel, make sure you have installed and enabled the babel-plugin-transform-class-properties plugin, or the stage-0 preset which includes it.

Other options for statically binding class methods would include using a @bind decorator (like decko's) or binding manually in a constructor:

export default class Foo extends Component {
    constructor(props, context) {
        super(props, context);
        this.setRef = this.setRef.bind(this);
    }
    setRef (ref) {
      this.foobar = ref;
    }
}

However, it's worth noting the resulting output simplicity when using the fat arrow technique:
https://goo.gl/TQJ44T

I had previously been promoting the use of @bind and similar solutions, but I think the ES5 output of the fat arrow property methods is quite pleasant.

All 5 comments

Hi @p3k: the reason setRef errors here is because without the fat arrow syntax, the method is not bound to the class instance. When preact calls your setRef function, it does not invoke it with context, so the value of this is always undefined. Using the fat arrow syntax or a locally defined function provides the desired value for this.

If you're getting an error when trying to transpile the fat arrow method syntax in Babel, make sure you have installed and enabled the babel-plugin-transform-class-properties plugin, or the stage-0 preset which includes it.

Other options for statically binding class methods would include using a @bind decorator (like decko's) or binding manually in a constructor:

export default class Foo extends Component {
    constructor(props, context) {
        super(props, context);
        this.setRef = this.setRef.bind(this);
    }
    setRef (ref) {
      this.foobar = ref;
    }
}

However, it's worth noting the resulting output simplicity when using the fat arrow technique:
https://goo.gl/TQJ44T

I had previously been promoting the use of @bind and similar solutions, but I think the ES5 output of the fat arrow property methods is quite pleasant.

I see, missing some basics of EcmaScript 6 here 😊 – thanks a lot for helping me out, anyway.

To make this work I had to enable the so-called stage-0 preset in Babel – which JSPM / SystemJS needs to be configured for differently, furthermore.

Now both, fat arrow syntax (which I am going to use following your recommendation) and the @bind decorator via decko, work for me.

_Seems I am in the middle of a steep learning curve…_ ➰

Excellent, glad you got it working. There's definitely a bit of a learning curve when adopting all these technologies at once, but it's worth it! I'll close this one out for now, but feel free to post any additional questions or feedback if you run into things.

@developit Does it really work though? When I use arrow function with class property, i get a DOM Element as this inside the handler. I found this line to be the culprit: https://github.com/developit/preact/blob/82e53f5e356094f81305c38543d4517125fb3e1d/src/dom/index.js#L109

Arrow functions don't transform to .bind() call, so when you use .call() on it, this is equal to whatever you pass to .call(), so the arrow function binding is not respected. If I use onClick={this.handleClick.bind(this)}, everything works as expected. Do you have to use .call(this,... in eventProxy? Is this necessary to pass the DOM Element to handlers?

I think it's clearly a bug and this.foobar = ref; is a very dangerous coincidence, because foobar will become the property of DOM Node.

This bug makes it impossible to access this.props, or this.state or any other instance property inside the class-property arrow-function handler.

@leonid-bauxy the implementation of ref is different from Events. It's always invoked on the component itself (implementation). Your other PR is definitely interesting though, I think we need to drop the .call there as you recommended.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

skaraman picture skaraman  Â·  3Comments

matuscongrady picture matuscongrady  Â·  3Comments

youngwind picture youngwind  Â·  3Comments

jescalan picture jescalan  Â·  3Comments

jasongerbes picture jasongerbes  Â·  3Comments