Reactify


In this series of blog posts, we’re looking at how to set up a simple web app in node.

In the previous installment we looked at building an API in node, and then consuming it from a web app using browserify to build node modules into a JavaScript file for the browser.

In this post we’ll look at using react to render the data on the screen.

Installing dependencies

We’ll need a couple more modules, lets install react, and reactify (which allows browserify to build react components).

> npm install react reactify --save

Creating a react component

React is a JavaScript library from Facebook for rendering user interfaces in the browser. It’s not a full feature framework (like Angular), instead it’s designed to handle the redrawing of UI components in response to changes in a data model. It takes the controversial decision to combine together logic and markup into reusable components. React also allows you to use JSX, which is a JavaScript where HTML is treated as a first-class concept. This can take a bit of getting used to. I like React. It’s a pleasure to work with, and I’ve found the re-usability of components very useful.

Now lets create a new JavaScript file in our client directory, to hold the react component to render our todos. Let’s call the file todo-list.js.

The file will return a react component, using the node style module.exports variable, like this:

var React = require('react');

module.exports = React.createClass({
  getInitialState:function(){
    return {text:''};
  },
  handleSubmit:function(e){
    e.preventDefault();
    this.props.onNewTodo(this.state.text);
    this.setState({text:''});
  },
  update:function(e){
    this.setState({text:e.target.value});
  },
  renderTodo : function(todo){
    return <li key={todo.key}>
      <strong>{todo.value}</strong> <a onClick={this.props.onDelete.bind(null, todo.key)} href="javascript:void(0);">delete</a>
    </li>
  },
  render:function(){
    return <div className="container">
      <h1>Todo List</h1>
      <ul>{this.props.todos.map(this.renderTodo)}</ul>      
      <form className="form-inline" onSubmit={this.handleSubmit}>
        <div className="form-group">
          <label>New todo</label>
          <input type="text" value={this.state.text} onChange={this.update} className="form-control" />
        </div>
        <button type="submit" className="btn btn-default">Add</button>
      </form>
    </div>
  }
});

There is a lot going on there, lets break it down.

  1. On the first list we require react. This allows us to create react components.
  2. We then call React.createClass to create the react component, and export it on the module.exports variable. This allows other files to require this one, and use the component.
  3. The component has a getInitialState function, which returns an initial state for our component. We’re using state to track the current value for the new todo that a user is entering. We’ll return a state where the text is an empty string.
  4. The handleSubmit function handles the form post when a new todo is created. It calls a onNewTodo function that must be set on the component when it’s used. It also resets the text to empty.
  5. The update function is called when the user types into the text box, we’ll update the state accordingly.
  6. The renderTodo function is called to render each todo. It returns the angle brackets to show the todo. Note that the onClick event for the delete button is bound to an onDelete function which must also be passed to the component.
  7. Finally we render the main page, calling the renderTodo function for each of the todos. We also bind the update function to the onChange event for the text box, so it can update the state as we type.

Ideally this component should be broken down into separate components: one for the todo list, and another with form for adding a new one. In the interests of simplicity I’ve kept them together.

Connecting the API

I have made the intentional decision not to call the API directly from the react component. Instead, I pass in functions which are called by the component when data needs to be saved/deleted. This separates out the concern of the data loading from the rendering, allowing us to re-use the component, or test it in isolation.

We therefore need to connect the API up with the component, to do this we’ll rewrite the index.js file:

var React = require('react');
var http = require('./http-request');
var Todos = require('./todo-list');

var content = document.getElementById('content');

function newTodo(text){
  http.post('/api', {todo:text}, render);
}

function deleteTodo(key){
  http.del('/api/' + key, render);
}

function render() {
  http.get('/api', function(err, data){
    React.render(<Todos todos={data} onNewTodo={newTodo} onDelete={deleteTodo} />, content);
  });
}

render();

The index file now has functions to call the API for adding and deleting todos. The render function gets all the todos from the API, and renders our react components, passing in the data from the API, as well as the functions which call the API. The render function gets called immediately, to show the component on application startup. The render function is also used as the the callback when todos are created/deleted on the API.

That’s all we need to do for now. We can build the react components by changing our command slightly to this (remember the reactify module we installed?):

> browserify client/index.js -t reactify | uglifyjs > public/index.min.js

You can start the application as before with:

> node server.js

The application should appear the same, only this time it’s a SPA!