scope of "this" in module.exports

125 views Asked by At

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.

2

There are 2 answers

0
HelloWorld On BEST ANSWER

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!

5
Alexander O'Mara On

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.