Updating the React one-hour-email

Motivation

In working on my first React app, I got stuck on the issue of inter-component communication and went looking for the recommended (“Reactive?”) method for solving this problem. The React docs have a few example apps linked with sample code, and also a section in the tips dedicated to this topic. Still, I wasn’t getting it.

And then I discovered the one-hour-email sample. This example contained the exact code for my use case: items in a list triggering an update of a ‘main’ component up-and-over in the component tree. Unfortunately, the example was written over a year ago based on React v0.3.2 and had not been updated since. React has gone through a number of changes since then; enough that the example code did not work out of the box with React v0.11.1. Here’s what I did to update the code and get it working again.

The Updates

My updated fork is available on github. See the full source or just the changes.

React to 0.11.1

Step one: update react and the jsx transformer to the latest version - 0.11.1. Instead of pulling them down into the repo, I chose to use the versions hosted by facebook.

index.html
-    <script src="components/react/react.js"></script>
-    <script src="components/react/JSXTransformer.js"></script>
+    <script src="http://fb.me/react-0.11.1.js"></script>
+    <script src="http://fb.me/JSXTransformer-0.11.1.js"></script>

Removing React.autoBind

After loading the page with the updated React library, I found this error regarding the React.autoBind wrapper for handleEmailSelected:

Uncaught TypeError: undefined is not a function

In React v0.4, autoBind became the default behavior. See this blog post for more details.

In this case, the fix was easy. Simply remove the autoBind wrapper:

app.js
-  handleEmailSelected: React.autoBind(function(index) {
+  handleEmailSelected: function(index) {
     var read = this.state.read;
     read[this.state.selected] = true;
     this.setState({selected: index, read: read});
-  }),
+  },

Component method binding

Warning message in the js console:

bind(): React component methods may only be bound to the component instance. See App

While digging for background on this error, I ran across this discussion. Based on this explanation, and the fact that I was passing this handler in via props, I changed the binding of the onEmailSelected callback from this.props to null:

app.js
         <EmailItem
-            onClick={this.props.onEmailSelected.bind(this.props, i)}
+            onClick={this.props.onEmailSelected.bind(null, i)}
             avatar={email.avatar}

Warning gone, and the component continues to function as normal.

DOM property class

At this point, I noticed that none of the styles were rendering. And there’s this accompanying warning:

Warning: Unknown DOM property class. Did you mean className?

Classes on DOM elements are set using the className property. According to the changelog, this was introduced in React v0.5.0.

All in all, 32 instances of class= had to be changed to className=. Here’s an example of two of them:

app.js
-      <div class="pure-u id-nav">
-          <a href="#nav" class="nav-menu-button">Menu</a>
+      <div className="pure-u id-nav">
+          <a href="#nav" className="nav-menu-button">Menu</a>
...

Making this change cleared the warning and, more importantly, fixed the css rendering on the page.

Key prop for array elements

Another warning in the console:

Each child in an array should have a unique "key" prop. Check the render method of List. See http://fb.me/react-warning-keys for more information.

The link in the warning takes you to the section on dynamic children in the tips. To add a key to all email items, I modified the JSON to add an id field:

app.js
 var SAMPLE_DATA = [
-  {"unread": false, "desc": "Hey, ...
-  {"unread": true, "desc": "Hey, ...
-  {"unread": true, "desc": "Duis ...
-  {"unread": false, "desc": "Excepteur ...
-  {"unread": false, "desc": "Ut ...
-  {"unread": false, "desc": "Mauris ...
+  {"id": 1, "unread": false, "desc": "Hey, ...
+  {"id": 2, "unread": true, "desc": "Hey, ...
+  {"id": 3, "unread": true, "desc": "Duis ...
+  {"id": 4, "unread": false, "desc": "Excepteur ...
+  {"id": 5, "unread": false, "desc": "Ut ...
+  {"id": 6, "unread": false, "desc": "Mauris ...
 ];

And in the List render function, I add the key property to each Item, concatenating the new id field with a string:

app.js
     var items = this.props.emails.map(function(email, i) {
       return (
         <EmailItem
+            key={'email-' + email.id}
             onClick={this.props.onEmailSelected.bind(null, i)}
             avatar={email.avatar}
             selected={this.props.selected === i}

Class list construction

Facebook released the classSet add-on to give a cleaner way to build class lists.

This code does not throw any errors, but does not leverage the classSet addon.

app.js
    var classes = 'email-item pure-g';
    if (this.props.selected) {
      classes += ' email-item-selected';
    }
    if (this.props.unread) {
      classes += ' email-item-unread';
    }

To refactor this code to use classSet, we need to load react-with-addons instead of plain react:

index.html
-    <script src="http://fb.me/react-0.11.1.js"></script>
+    <script src="http://fb.me/react-with-addons-0.11.1.js"></script>

And then make the change in app.js:

app.js
 var EmailItem = React.createClass({
   render: function() {
-    var classes = 'email-item pure-g';
-    if (this.props.selected) {
-      classes += ' email-item-selected';
-    }
-    if (this.props.unread) {
-      classes += ' email-item-unread';
-    }
+    var cx = React.addons.classSet;
+    var classes = cx({
+      'email-item pure-g': true,
+      'email-item-selected': this.props.selected,
+      'email-item-unread': this.props.unread
+    });

The end result is the same. And as expected, both ‘email-item’ and ‘pure-g’ classes get applied when supplied together with a hard ‘true’.

transferPropsTo

The React docs caution against using transferPropsTo, instead recommending that props be copied, as needed, to children. For now, I’ve left this line intact from the original example.

Sidenote: Ghostery and TypeKit

I use the Ghostery extension in Chrome, which blocked the Adobe TypeKit js library used in the original example (http://use.typekit.net/ajf8ggy.js). Disabling Ghostery for localhost removed the net::ERR_BLOCKED_BY_CLIENT error in the js console.

Back