Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event handler 'this' context when using transferPropsTo #1595

Closed
danielstocks opened this issue May 24, 2014 · 5 comments · Fixed by #1597
Closed

Event handler 'this' context when using transferPropsTo #1595

danielstocks opened this issue May 24, 2014 · 5 comments · Fixed by #1597

Comments

@danielstocks
Copy link

Hello,

Not sure if this is a bug or a feature :)

I have a set of React components that share the same behaviour for events so I created a mixin and used the getDefaultProps/transferPropsTo methods to share this functionality.

var Highlight = {
  getDefaultProps: function() {
    return {
      onMouseOver : this.highlight
    }
  },
  highlight: function() {
    console.log(this) //<--- window
  },
  unhighlight: function() {
    console.log(this) //<--- Paragraph component
  }
}

var Paragraph = React.createClass({
  mixins: [Highlight],
  render: function() {
    return this.transferPropsTo(
      <p onMouseOut={this.unhighlight}>Hello</p>
    )
  }
});

As you can see, when adding the event in JSX the local scope of the event callback is binded to the component, but when using getDefaultProps 'this' points to 'window'.

A workaround is obviously to rebind the scope manually

getDefaultProps: function() {
  return {
    onMouseOver : this.highlight.bind(this)
  }
},

But I'm wondering if the behaviour of getDefaultProps/transferPropsTo should be more constant with setting the properties in JSX? Or maybe there's a better way to inherit event handlers to components altogether?

Here's a JS fiddle with the above example code: http://jsfiddle.net/kb3gN/2703/

Thanks in advance

Daniel

sophiebits added a commit to sophiebits/react that referenced this issue May 25, 2014
@sophiebits
Copy link
Collaborator

Note that after my change was made, @sebmarkbage made a change to cache the result of getDefaultProps across instances so now you can't refer to the current instance as this there. The proper solution is to do:

<p onMouseOver={this.handleMouseOver}>...</p>

handleMouseOver: function(e) {
  if (this.props.onMouseOver) {
    return this.props.onMouseOver(e);
  }
  // default behavior here
}

@danielstocks
Copy link
Author

I don't understand. Can you show me an example involving getDefaultProps & transferPropsTo, has that changed in any way?

@sophiebits
Copy link
Collaborator

In getDefaultProps you can't access this any more; it is called only once and refers to the class instead of the new instance being created. That is, if you have

var A = React.createClass({
  getDefaultProps: function() {
    console.log("getting default props!");
    return {width: 120};
  }
});
React.renderComponent(<A />, node1);
React.renderComponent(<A />, node2);

then the default props will be looked up only once and shared, instead of twice as they were before. There's no way to do what your original example is aiming for using only transferPropsTo. You have to specify the handler explicitly when rendering.

@danielstocks
Copy link
Author

Hmmm ok. That's kind of a breaking change. I've seen a lot of people use this pattern to share properties and behaviour between components. How would you recommend architecting the first code example if I wanted to share the Highlight mixin between two different components?

I could as you say specify the handlers on the actual components, but in this case it's not so DRY because i have like 20-30 components sharing the same behaviour. Adding a new event handler to that mixin will require me to edit all these components instead of just the mixin.

@sophiebits
Copy link
Collaborator

It is a breaking change. That's why I'm telling you here and why it'll be mentioned prominently in the release notes. Mixins are generally not the preferred way to share functionality. I don't know you exact use case but you might be able to do something similar with composition like this:

var Highlight = React.createClass({
  highlight: function() {
  },
  unhighlight: function() {
  },
  render: function() {
    return (
      <div onMouseOver={this.highlight} onMouseOut={this.unhighlight}>
        {this.props.children}
      </div>
    );
  }
});

var Paragraph = React.createClass({
  render: function() {
    return (
      <Highlight>
        <p>Hello</p>
      </Highlight>
    );
  }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants