# React components with ES6

Source: https://tpiros.dev/blog/react-with-es6

I recently met [Nicola](https://twitter.com/durdn) at a conference and caught his enthusiasm about React immediately. It took a few weeks to sit down and write my first [React](https://facebook.github.io/react/) component. Quick background if you're new to React: it pushes component-based programming. Small, reusable, loosely coupled pieces that follow the separation of concern principle.

Since I was brand new to React, I started with their [tutorial](https://facebook.github.io/react/docs/tutorial.html) that walks through building a component. Good starting point, but I wanted to push further. Back in January, the team [announced](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html) that React would support ES6 features like classes. Challenge accepted. I'd rebuild the tutorial component using ES6.

Let's talk setup first. I'd spent time reading about ES6, listening to presentations, and finally rolled up my sleeves. My preferred ES6-to-ES5 compiler is [Babel](babeljs.io/), installed via `npm install -g babel`. I keep an `src/es6` folder for my `app.es6` file. To compile: `babel src/es6/app.es6 --out-file src/app.js`. Then run React's `jsx` on it: `jsx src/ build/`.

Now, the actual conversion from ES5 to ES6. A few things to remember, starting with the simplest: ES6 classes.

This code defines a React component and transforms easily into ES6:

```js
var CommentBox = React.createClass({
  render: function () {
    return <div className="commentBox">Hello, world! I am a CommentBox.</div>;
  },
});
React.render(, document.getElementById('content'));

class CommentBox extends React.Component {
  render() {
    return <div className="commentBox">Hello, world! I am a CommentBox.</div>;
  }
}
React.render(, content);
```

So far so good. The tutorial later adds a `getInitialState` method to CommentBox. This method fires once before the component mounts, and its return value becomes `this.state`. Since we're listing comments, we initialise with an empty array:

```js
var CommentBox = React.createClass({
  getInitialState: function () {
    return { data: [] };
  },
  render: function () {
    // more code here
  },
});
```

Here's the ES6 version. If you read the [documentation](https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#es6-classes) carefully, you'll notice there's no need for `getInitialState` with classes. We use the `constructor()` instead:

```js
class CommentBox extends React.Component {
  constructor() {
    this.state = { data: [] };
  }

  render() {
    // more code here
  }
}
```

The `constructor()` sets up the initial state for the data array (an empty array, in this case).

Now for the part that gave me a headache. The tutorial shows this code for loading data from an external file (let's call it `comments.json`):

```js
[
  {"author": "Tamas", "text": "Comment from Tamas"},
  {"author": "Mark", "text": "Comment from Mark"}
]

var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments
        
        
      </div>
    );
  }
});

React.render(
  ,
  document.getElementById('content')
);
```

Looks clear enough. Let's convert it to ES6:

```js
class CommentBox extends React.Component {
  constructor() {
    this.state = { data: [] }
  }

  loadCommentsFromServer() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: (data) => {
        this.setState({data: data});
      },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString());
      }
    });
  }

  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  }

  render() {
    return <div className="commentBox">
    <h1>Comments
    
    </div>
  }
}

React.render(, content);
```

Run this and the comments load fine. But after 2 seconds (the `pollInterval`), you'll hit this error in the console:

Curious. Let's drop a `console.log(this);` into `loadCommentsFromServer()` just before the AJAX call. The result tells the whole story:

The first `this` points to CommentBox. Every subsequent log statement prints the `Window` object (JavaScript's global in the browser). No wonder the URL property is undefined. The fix? Keep `loadCommentsFromServer` bound to the right scope. Add `.bind(this)`:

```js
componentDidMount() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer.bind(this), this.props.pollInterval);
  }
```

That sorts it out permanently. Here's the full ES6 version of the component:

```js
class CommentList extends React.Component {
  render() {
    var commentNodes = this.props.data.map((comment) => {
      return ()
    });
    return (<div className="commentList">
    {commentNodes}
    </div>)
  }
}

class CommentForm extends React.Component {
  handleSubmit(e) {
    e.preventDefault();
    var author = React.findDOMNode(this.refs.author).value.trim();
    var text = React.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) {
      return;
    }
    this.props.onCommentSubmit({author: author, text: text});
    React.findDOMNode(this.refs.author).value = '';
    React.findDOMNode(this.refs.text).value = '';
    return;
  }

  render() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
      <input type="text" placeholder="Your name" ref="author" />
      <input type="text" placeholder="Your comment" ref="text" />
      <input type="submit" value="Post" />
      </form>
    )
  }
}

class CommentBox extends React.Component {
  constructor() {
    this.state = { data: [] }
  }

  loadCommentsFromServer() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: (data) => {
        this.setState({data: data});
      },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString());
      }
    });
  }

  handleCommentSubmit(comment) {
    var comments = this.state.data;
    var newComments = comments.concat([comment]);
    this.setState({data: newComments});
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: (data) => {
        this.setState({data: data});
      },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString());
      }
    });
  }

  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer.bind(this), this.props.pollInterval);
  }

  render() {
    return <div className="commentBox">
    <h1>Comments
    
    
    </div>
  }
}

class Comment extends React.Component {
  render() {
    return <div className="comment">
    <h2 className="commentAuthor">{this.props.author}
    {this.props.children}
    </div>
  }
}

React.render(, content);
```

To run this, you'll need two packages: `npm install babel` and `npm install react-tools`.

First, convert ES6 to ES5: `babel src/to/app.es6 --out-file src/app.js`. Then run it through React's JSX tool: `jsx src/ build/`. The build folder will contain your `.js` file, ready to include in HTML:

```html
<div id="content"></div>
<script src="build/app.js"></script>
```

React's been a genuinely exciting library to work with. I'm looking forward to rolling up my sleeves again and building more components, hopefully with even more ES6.
