Reputation: 2330
I am turning a React.js component into a Common.js module using module.exports and am having an issue accessing "this" in the context of the component element from one of it's methods.
Below is the entire component. I have placed a comment above the line where the problem occurs. I did try a less verbose example at first but I do not think it was sufficient to explain the issue.
var React = require('react');
var GSAP = require('gsap');
var Psychedelicon = React.createClass({
cycleColors: function() {
var touchPlatforms = ['iPhone', 'iPad', 'iPod', 'Android', 'Linux armv7l', 'WinCE'];
isTouch = false;
iDevice = false;
isDroid = false;
plat = navigator.platform;
if(plat === 'iPhone' || plat === 'iPad' || plat === 'iPod') {
isTouch = true;
iDevice = true;
}
else if (plat === 'Linux armv7l' || plat === 'Android') {
isTouch = true;
isDroid = true;
}
else {
for (var i = 0; i < touchPlatforms.length; i++) {
if (plat === touchPlatforms[i]) {
isTouch = true;
break;
}
else {
isTouch = false;
}
}
}
var isIE = false
if (navigator.userAgent.toLowerCase().indexOf('msie') > -1 || navigator.userAgent.toLowerCase().indexOf('trident') > -1) {
isIE = true
}
var isFF = false
if (navigator.userAgent.toLowerCase().indexOf('firefox') != -1) {
isFF = true
}
if(!isTouch) {
var ColorSwirl = function(colorSet,defaultColor,time) {
var storedResult;
var randomColor = function(theArray) {
var result = theArray[Math.floor(Math.random() * (theArray.length))];
if(result === storedResult){
return(defaultColor)
}
else {
storedResult = result;
return(result);
}
}
var theLuckyColors = {top:randomColor(colorSet),bottom:randomColor(colorSet)};
var swirl = function(){
//!!!!On this line the problem occurs onUpdateParams must reference the element accepting the execution event (onMouseEneter)
TweenLite.to(theLuckyColors, time, {colorProps:{top:randomColor(colorSet), bottom:randomColor(colorSet)}, onUpdate:colorize, onUpdateParams:[this],onComplete:swirl});
}
gradients
var colorize = function(el) {
if(isIE) {
TweenLite.set(el, {
backgroundImage:'-ms-radial-gradient(center,circle cover,' + theLuckyColors.top + ' 0%, ' + theLuckyColors.bottom + ' 100%)'
});
}
else if(isFF) {
TweenLite.set(el, {
backgroundImage:'-moz-radial-gradient(center,circle cover,' + theLuckyColors.top + ' 0%, ' + theLuckyColors.bottom + ' 100%)'
});
}
else {
TweenLite.set(el, {
backgroundImage:'radial-gradient(circle,' + theLuckyColors.top + ', ' + theLuckyColors.bottom + ')',
backgroundImage:'-webkit-radial-gradient(circle,' + theLuckyColors.top + ', ' + theLuckyColors.bottom + ')'
});
}
}
swirl();
}
ColorSwirl(['red','green','#4B0082','#9F00FF','yellow','orange'],'blue',.15);
}
},
stopTheCycle: function() {
},
render: function() {
return (
<a className="psychedelicon" href={this.props.href} target={this.props.target} onMouseEnter={this.cycleColors} onMouseLeave={this.stopTheCycle}>
<i className={"fa fa-" + this.props.icon}></i>
</a>
)
}
});
module.exports = Psychedelicon;
So far I have tried to bind "this" to the the element receiving the event:
onMouseEnter={this.cycleColors.bind(this)}
and I got: `'You are binding a component method to the component. React does this for you automatically in a high-performance way, so you can safely remove this call.'
I also tried:
onMouseEnter={this.cycleColors.call(Psychedelicon)}
and onMouseEnter={this.cycleColors.bind(Psychedelicon)}
which both produced no error but did not work
I know that the function otherwise works because when I change
onUpdateParams:[this]
to
onUpdateParams:['.psychedelicon']
The component produces the desired behavior, except that it effects all of the components at the same time (which I need to avoid hence needing to use "this").
I must be missing something. Any help is appreciated.
Upvotes: 0
Views: 125
Reputation: 60527
UPDATE: This answer does not apply to React, but was in response to a more-general previous version of the question.
This looks like another argument for not using the onclick
attribute, but you could use the call
or apply
method, and pass this
as the first argument.
<div id="foo" onClick="Module.addClass.call(this)"></div>
However you might want to consider using addEventListener
or jQuery's event delegation instead.
Upvotes: 1
Reputation: 2330
So I was able to solve my own problem. Here is the code that did the trick:
var React = require('react');
var GSAP = require('gsap');
var $ = require('jquery')
var Psychedelicon = React.createClass({
componentDidMount: function() {
var that = React.findDOMNode(this.refs.psicon);
$(that).hover(function() {
//detect device type for Psychedelicon
var touchPlatforms = ['iPhone', 'iPad', 'iPod', 'Android', 'Linux armv7l', 'WinCE'];
isTouch = false;
iDevice = false;
isDroid = false;
plat = navigator.platform;
if(plat === 'iPhone' || plat === 'iPad' || plat === 'iPod') {
isTouch = true;
iDevice = true;
}
else if (plat === 'Linux armv7l' || plat === 'Android') {
isTouch = true;
isDroid = true;
}
else {
for (var i = 0; i < touchPlatforms.length; i++) {
if (plat === touchPlatforms[i]) {
isTouch = true;
break;
}
else {
isTouch = false;
}
}
}
//sniff the for ie
var isIE = false
if (navigator.userAgent.toLowerCase().indexOf('msie') > -1 || navigator.userAgent.toLowerCase().indexOf('trident') > -1) {
isIE = true
}
//sniff for firefox
var isFF = false
if (navigator.userAgent.toLowerCase().indexOf('firefox') != -1) {
isFF = true
}
//Begin ColorSwirl on non-touch devices
if(!isTouch) {
//Define the Color Sets
var ColorSwirl = function(colorSet,defaultColor,time) {
//Pick random color. If the color is the same as the previous one pick blue instead.
var storedResult;
var randomColor = function(theArray) {
var result = theArray[Math.floor(Math.random() * (theArray.length))];
if(result === storedResult){
return(defaultColor)
}
else {
storedResult = result;
return(result)
}
}
//Pick our colors for the initial state
var theLuckyColors = {top:randomColor(colorSet),bottom:randomColor(colorSet)};
//Start swirling
$(that).addClass('swirling');
var swirl = function(){
if($(that).hasClass('swirling')) {
TweenLite.to(theLuckyColors, time, {colorProps:{top:randomColor(colorSet), bottom:randomColor(colorSet)}, onUpdate:colorize, onUpdateParams:[that],onComplete:swirl});
}
}
//Detect Browser and Pass Psychedelicon the appropriate radial gradients
var colorize = function(el) {
if(isIE) {
TweenLite.set(el, {
backgroundImage:'-ms-radial-gradient(center,circle cover,' + theLuckyColors.top + ' 0%, ' + theLuckyColors.bottom + ' 100%)'
});
}
else if(isFF) {
TweenLite.set(el, {
backgroundImage:'-moz-radial-gradient(center,circle cover,' + theLuckyColors.top + ' 0%, ' + theLuckyColors.bottom + ' 100%)'
});
}
else {
TweenLite.set(el, {
backgroundImage:'radial-gradient(circle,' + theLuckyColors.top + ', ' + theLuckyColors.bottom + ')',
backgroundImage:'-webkit-radial-gradient(circle,' + theLuckyColors.top + ', ' + theLuckyColors.bottom + ')'
});
}
}
swirl();
}
ColorSwirl(['red','green','#4B0082','#9F00FF','yellow','orange'],'blue',.15);
}
},function() {
var theLuckyColors = {top:'#FFFFFF',bottom:'#FFFFFF'};
var stopNow = function(time){
$(that).removeClass('swirling');
TweenLite.to(theLuckyColors, time, {colorProps:{top:'#FFFFFF', bottom:'#FFFFFF'}, onUpdate:whiteWash, onUpdateParams:[that]});
}
var whiteWash = function(el) {
TweenLite.set(el, {
backgroundImage:'-ms-radial-gradient(center,circle cover,#FFFFFF 0%, #FFFFFF 100%)',
backgroundImage:'-moz-radial-gradient(center,circle cover,#FFFFFF 0%, #FFFFFF 100%)',
backgroundImage:'radial-gradient(circle,#FFFFFF,#FFFFFF)',
backgroundImage:'-webkit-radial-gradient(circle,#FFFFFF,#FFFFFF)'
});
}
stopNow(.15);
});
},
render: function() {
return (
<a className="psychedelicon" ref="psicon" href={this.props.href} target={this.props.target} onMouseEnter={this.cycleColors} onMouseLeave={this.stopTheCycle}>
<i className={"fa fa-" + this.props.icon}></i>
</a>
)
}
})
module.exports = Psychedelicon;
Here is how I got from the problem to the solution:
When I was unable to produce a result by using "call" as was suggested by @Alexander O'Mara, i required jQuery to speed up testing and added the variable
var that = $(this)
to the component's outermost scope, so that I could access the component itself from the scope of the inner functions like so:
//Note that onUpdateParams now references "that" which is equal to "this" in the scope of the actual component.
TweenLite.to(theLuckyColors, time, {colorProps:{top:randomColor(colorSet), bottom:randomColor(colorSet)}, onUpdate:colorize, onUpdateParams:[that],onComplete:swirl});
this failed again so I logged the value of "this" to the console and saw that I was actually referencing the component's constructor and not the rendered output!
I had a look at the docs again and saw that I could reference the rendered output on every render instance by using a reactjs attribute called "refs". I needed only to give the rendered element a "ref" attribute:
render: function() {
return (
<a className="psychedelicon" ref="psicon" href={this.props.href} target={this.props.target} onMouseEnter={this.cycleColors} onMouseLeave={this.stopTheCycle}>
<i className={"fa fa-" + this.props.icon}></i>
</a>
)
}
and reference the ref in my method, which I decided to run out of "componentDidMount" instead.
var that = React.findDOMNode(this.refs.psicon);
Now, everytime I reference "that" I am referencing the rendered element itself (pretty impresive considering it's re-rendering every .15 seconds on mouseover) and everything is peachy!
Upvotes: 1