How to Animate Simple Text Change in ReactJS

I wanted to show the fade in effect for the text change on an element in ReactJS. I looked into ReactCSSTransitionGroup, and noticed it could not serve my need. ReactCSSTransitionGroup does very well animate the added and removed DOM node. If I just want to animate a text change without a DOM change, I will need to find a new solution.

Finally, here is my solution:

CSS

@-webkit-keyframes jump {
  0% {
    opacity: 1;
  }

  50% {
    opacity: 0
  }

  100% {
    opacity: 1;
  }
}

.jumpie {
  -webkit-animation-duration: 1s;
  -webkit-animation-iteration-count: 1;
  -webkit-animation-timing-function: linear;
}

The parent component sending down the text change.

module.exports = React.createClass({
  getInitialState: function () {
    return {
      counter: 0
    };
  },
  componentDidMount: function () {
    var self = this;
    setInterval(function () {
      self.setState({
        counter: self.state.counter + 1
      })
    }, 2000);
  },
  render:function () {
    return (

); } })

The child element animates the text change.

import React from 'react';

module.exports = React.createClass({
  getInitialState: function () {
    return {
      text: this.props.text
    };
  },

  componentDidMount: function () {
    React.findDOMNode(this.refs.jumpie).addEventListener('webkitAnimationEnd', function () {
      this.style.webkitAnimationName = '';
    });
  },

  componentWillReceiveProps: function (nextProps) {
    var self = this;
    if (nextProps.text !== this.props.text) {
      React.findDOMNode(this.refs.jumpie).style.webkitAnimationName = 'jump';
      setTimeout(function () {
        self.setState({
          text: nextProps.text
        });
      }, 500); //This is half the animation time we defined earlier in the CSS animation
    }
   },

  render: function () {
    return ({this.state.text});
    }
  });

 

15531_student_ba_assoc_728x90

Beloved JavaScript Pattern – Singleton

There are many ways to write a Singleton in JavaScript to meet your needs. I love anonymous functions, which empowers to do almost everything, if you need an object! Recently, as I wrote, I found a very precise way to write Singleton. It looks like the way Prototype is created, but it is a Singleton. It makes me happy!

Example:

var Counter = {
    current: 0,
    increment: function () {
        return (++this.current);
    },
    decrement: function () {
        if(--this.current) {
            this.reset();
        }
        return this.current;
    },
    reset: function () {
        return (this.current = 0);
    }
};

Isn’t it that simple? It is really tidy, and can get things done.

Live Nodes!

Thank to several passionate people I talked to recently, I learned about the liveness of some DOM objects. These objects remain live and updated even after the layout is changed. The two live objects are NodeList and NamedNodeMap.

NodeList

Definition

From http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-536297177

The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeListobjects in the DOM are live.

Example

>>var nodeList = document.getElementsByTagName('div');
>> nodeList
NodeList[400]
>> var newNode = document.createElement('div');
>> document.body.appendChild(newNode);
>> nodeList
NodeList[401]

The same variable nodeList has got a updated list of node.

Key points

  • This collection is auto updated, so you need to query again if you still want to refer this set of nodes, even after the markup has been changed.
  • Faster collecting elements. getElementsByTagName() is significantly faster than querySelectorAll(). See explanation in Nicholas Zakas’s post Why is getElementsByTagName() faster that querySelectorAll()?
  • It is supported in most of the browsers, even though some of them are buggy. See SitePoint.
  • Do not use the length of the updated nodeList as a criteria for breaking the loop. Otherwise, it will end up with never ending running script.

NamedNodeMap

Definition

From http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1780488922

Objects implementing the NamedNodeMap interface are used to represent collections of nodes that can be accessed by name. Note that NamedNodeMap does not inherit from NodeList; NamedNodeMaps are not maintained in any particular order. Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index, but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, and does not imply that the DOM specifies an order to these Nodes. NamedNodeMap objects in the DOM are live.

Example

From SitePoint

>> var attrs = document.getElementsByTagName('link')[0].attributes;
>> attrs
NamedNodeMap
0: Attr
1: Attr
length: 2
__proto__: NamedNodeMap
>> var media = document.createAttribute('media');
>> media.nodeValue = 'all';
>> attrs.setNamedItem(media);
NamedNodeMap
0: Attr
1: Attr
2: Attr
length: 3
__proto__: NamedNodeMap

Conclusion

getElementsByTagName() and attributes are live HTML DOM objects. It keeps updating while markups are being changed. We will benefit if we make good use of them.

prevenDefault and stopPropagation

These were the two event methods I hadn’t paid much attention to. I could use them correctly for most of scenarios. When it came to more complicated cases, I was baffled. So let’s look into them more closely.

stopPropagation

From W3C – Document Object Model Events:

The stopPropagation method is used prevent further propagation of an event during event flow. If this method is called by any EventListener the event will cease propagating through the tree. The event will complete dispatch to all listeners on the current EventTarget before event flow stops. This method may be used during any stage of event flow.

I have two catches from this definition:

  1. When this method is called, the event will not stop until it dispatch on the current event target;
  2. It seems to have not connection with DOM implementation, which is different from preventDefault.

preventDefault

From W3C – Document Object Model Events:

If an event is cancelable, the preventDefault method is used to signify that the event is to be canceled, meaning any default action normally taken by the implementation as a result of the event will not occur. If, during any stage of event flow, the preventDefault method is called the event is canceled. Any default action associated with the event will not occur. Calling this method for a non-cancelable event has no effect. Once preventDefault has been called it will remain in effect throughout the remainder of the event’s propagation. This method may be used during any stage of event flow.

So what about cancelable?

Some events are specified as cancelable. For these events, the DOM implementation generally has a default action associated with the event. An example of this is a hyperlink in a web browser. When the user clicks on the hyperlink the default action is generally to active that hyperlink. Before processing these events, the implementation must check for event listeners registered to receive the event and dispatch the event to those listeners. These listeners then have the option of canceling the implementation’s default action or allowing the default action to proceed. In the case of the hyperlink in the browser, canceling the action would have the result of not activating the hyperlink.

From the definitions, there are the following 3 key points:

  1. This cancelation is for the actions in DOM implementation, not event listeners.
  2. Event listener takes precedence over the default DOM action, and it has the ability to cancel the DOM action.
  3. This event may be canceled during any stage of event flow, so it may not happen only on the very element node that the event has started.

Here are the example:

Example 1:

HTML:

<a id="test-link" href="http://www.google.com/">Link</a>

JavaScript:

var link = document.getElementById('test-link');
link.addEventListener('click', function (e) {
    e.preventDefault();
}, false);

This event listener cancels the default action of this HREF link, which is very clear.

Example 2:

HTML:

<a id="test-link" href="http://www.google.com/"><img src="/image.png" alt="" id="test-image" />

JavaScript:

var img = document.getElementById('test-image');
img.addEventListener('click', function (e) {
    e.preventDefault();
}, false);

However, this event listener actually cancels the default action of the parent of the element being clicked. This was something I was really happy to find out.

Private Member in MooTools (kind of)

MooTools makes prototypical JavaScript look like a class-based language. In JavaScript, if you want to keep something private, keep it in a private scope. The question, how big is the scope? We can do:

(function () {

function calcAge(birthDate) {
     return Math.floor(((new Date).getTime() - birthDate.getTime())/1000/60/60/24/365);
}

var Dog = new Class({
    initialize: function (name, birthDate) {
        this.name = name;
        this.birthDate = new Date(birthDate);
    },

    getAge: function () {
        return calcAge(this.birthDate);
    }
});

var harry = new Dog('harry', '2000-2-10');

console.log(harry.getAge());

}());

The function calcAge is not part of the class Dog, but I wanted it to be part of the class, so that other classes may not be able to get access to it. Then I came up with the following pattern:

var Dog = (function () {
    function calcAge(birthDate) {
        return Math.floor(((new Date).getTime() - birthDate.getTime())/1000/60/60/24/365);
    }

    return new Class({
        initialize: function (name, birthDate) {
            this.name = name;
            this.birthDate = new Date(birthDate);
        },

        getAge: function () {
            return calcAge(this.birthDate);
        }
    });
}());

var harry = new Dog('harry', '2000-2-10');

console.log(harry.getAge());

Now the function calcAge is hiding perfectly inside the class Dog. All the methods of the class can get access to this private function calcAge. However, this private member cannot get access to the data associated with an instance of the class. Therefore, this pattern is used for capitulating private methods only. (I have not come up with a way to properly keep private variables. Still working on it)

Suppress Error Message Popup in IE

It is notorious that IE lacks of JavaScript debugging tools. When a runtime error happens, big or small, IE provides very limited information about the error. Fortunately, I found JavaScript Stacktrace. It supports all browsers. Based on that, I built a JavaScript stack trace popup to help our team better debug in this stubborn kid – IE.

window.onerror = function () {
    //JsErrorPopUp is the module for my customized popup.
    new JsErrorPopUp(printStackTrace());
}

However, we still found the IE error message icon at the status bar or the message alert box unpleasant to see. I want to suppress it. These are the 2 solutions I come up with.

Solution 1:

add try…catch, without throwing the exception in IE.

To catch all the errors, I need to wrap our entire JavaScript application with the try block.

It does not look pretty, but I still cannot convince myself that if the try/catch block will hurt performance. If you can prove try/catch makes code slow, please feel free to drop a line.

Solution 2:

Add return true; in the onerror callback.

window.onerror = function () {
	new JsErrorPopUp(printStackTrace());
	return true;
}

Simple, clean, and makes sense.

Double Equals Is Weird, But Not

I’m on my way to Portland, OR. Michael is attending the 2011 ACL (The Association for Computational Linguistics) conference there. I’m a fan of natural language processing, but this time I will only focus on sightseeing.

While I’m waiting for boarding, I would like to mention something my colleague Mindy mentioned to me about some weird things in JavaScript. For example,

0 == '0' //true
false == undefined //false

I read through The Equals Operator (11.9.3 The Abstract Equality Comparison Algorithm) of the 5th edition of ECMA-262 specs on the flight to Portland. I realized so many things I used, I assumed, and I misused had become crystal clear, including the weird things Mindy pointed out. You will understand me if you know the feeling of knowing a good friend better. 🙂

In this chapter (11.9.3), if you see step 4 and 5:

4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.

You will know why this weirdness could happen:

0 == '0' //true
100 == '100' //true
0 = '0' //true
'100' == 100 //true

Also see step 6:

If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.

Therefore:

false == undefined //false

See 1.c.i and ii:

c. If Type(x) is Number, then
i. If x is NaN, return false.
ii. If y is NaN, return false.

NaN == NaN //false

See 6 and then 5:

5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.

'' == false //true

According to step 10, the rest of cases will return false:

10. Return false.

'' == undefined //false

(Actually it makes sense that, as long as a value has been declared with a value. It cannot be undefined any more)

Also because of step 10, it yields:

'' == null //false

Conclusion

Therefore, they look weird, but they are not. After reading the algorithm, I can see the intention behind the weirdness.

I remember many JavaScript authorities have mentioned that it is better to use triple equals, because it could avoid so many uncommon cases like those I have listed above. If you take a look at the 11.9.6 The Strict Equality Comparison Algorithm, the triple equals comparison is a lot straightforward.

I’m in Eugene, OR now. Almost there! Sometimes, you have to pay time for the free flight ticket.

Updated on June 27, 2011

isNaN is not sufficient to test if the value is NaN. Why?

isNaN('string') === true //true

Therefore the sufficient conditions should be:

typeof a === 'number' && isNaN(a)