Mar 2016

22

Rethinking web apps with React & Alt - Part 1

React.JS lets you create composable view components. Flux is an application architecture for building complex User Interfaces. It allows you to handle data flows throughout your application. It is a pattern rather than a formal framework. Unlike Angular and Knockout, the data flows in a uni-directional. What this means is that data enters through a single place (Actions) and then flow outward through the State managers (Store), finally to the View.

Why Alt?

There are several alternative implementations to Flux viz. Alt, Reflux, Fluxxor, Redux, Fluxible, et al. I will be using the Alt framework instead of Facebook's Flux. To read more about Facebook's Flux click here. My choice of going with Alt instead of Flux was based on this blog.

To quickly summarize the conclusions from the blog. Alt provides the following advantages

  1. Awesome helpers
  2. A more ES5 friendly syntax
  3. Better support for non-React views
  4. Extremely responsive community

This will be a 3 part blog. At the end of this blog we'll have a fully functional Todo App with React & Alt. In the first part we'll start with creating the backend JSON API along with listing all Todos. I am using Rails here, you should be fine using anything else as long as the data returned from the various end-points are the same. In the second part we'll deep dive into Props and state and use that knowledge to create a new Todo. In the last section we'll add the delete, clear completed and update Todo Feature.

Rails Setup

To start with a new rails project do the following


rails new rails-react-alt-todo-app -T  --skip-bundle
cd rails-react-alt-todo-app
          

We'll clean up the comments in the Gemfile and add a couple of things. Add 'react-rails' and instead of byebug we'll be using 'pry-byebug'. I also like to use 'hirb' to format the console output, this is optional.


source 'https://rubygems.org'

gem 'rails', '4.2.5.1'
gem 'sqlite3'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'jquery-rails'
gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'react-rails'

group :development, :test do
  gem 'pry-byebug'
  gem 'hirb'
end

group :development do
  gem 'web-console', '~> 2.0'
  gem 'spring'
end
          

Before running the migration let us bundle install and then create a new migration for the todos table. The react:install generation script creates a components.js manifest file and also creates a directory called apps/assets/javascripts/components. We will not be using this directory, we will rename this to apps/assets/javascripts/react. But we'll do it later, for now leave it as is.


bundle install
rails g react:install
rails g migration create_todos
          

class CreateTodos < ActiveRecord::Migration
  def change
    create_table :todos do |t|
      t.string :name
      t.boolean :completed

      t.timestamps null: false
    end
  end
end
          

Cleanup the routes file and we'll start with just one route for todos#index and we'll redirect the root_path to this action. Start with creating a todos_controller with just the index action. We'll make a few changes to the application.html.erb and include a partial for the navigation bar. You can find a copy of the react.png image here, you can download it and save it under assets/images. Create a new directory /assets/fonts/ and download glypicons from here and save the aot, svg, ttf and woff extensions under /assets/fonts directory. You will need to make one change to config/applications.rb and create a new file app/assets/stylesheets/fonts.css.scss file so that the new fonts are picked up.


# config/routes.rb
Rails.application.routes.draw do
  resources :todos, only: [:index]
  root 'todos#index'
end

# app/controllers/todos_controller.rb
class TodosController < ApplicationController
  def index
    @todos = Todo.all
  end
end

# add this line to the bottom of config/application.rb
config.assets.paths << "#{Rails}/vendor/assets/fonts"
          

While we are creating the fonts.css.scss, let us create a new scss file with our custom styles.


/* app/assets/stylesheets/fonts.css.scss */
@font-face {
    font-family: 'Glyphicons Halflings';
    src: asset-url('/assets/glyphicons-halflings-regular.eot');
    src: asset-url('/assets/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
         asset-url('/assets/glyphicons-halflings-regular.woff') format('woff'),
         asset-url('/assets/glyphicons-halflings-regular.ttf') format('truetype'),
         asset-url('/assets/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
}
/* app/assets/stylesheets/todos.css.scss */
.outer-container {
  width: 80%;
  margin: 10px auto;
}

.finished {
  text-decoration: line-through;
  color: lightgray;
  font-style: italic;
}

.input-group {
  margin: 10px 0;
}

.add-todo-group {
  margin-bottom: 30px;
}

.btn-clear-group {
  margin-top: 30px;
  text-align: right;
}

.navbar-img {
  margin-top: 5px;
}

.navbar {
  border-radius: 0;
}
          

Let us create a layout helper that will help us include some javascripts and stylesheets, specifically bootstrap and lodash. Once that is done, we can easily reference the various version in our application layout template.


module LayoutHelper
  def bootstrap_version
    '3.3.6'
  end

  def lodash_version
    '4.6.1'
  end
end
          

<!-- views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
  <title>Todos with React & Altlt;/title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="//netdna.bootstrapcdn.com/bootstrap/<%= bootstrap_version %>/css/bootstrap.min.css" rel="stylesheet">
  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <script src="//netdna.bootstrapcdn.com/bootstrap/<%= bootstrap_version %>/js/bootstrap.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/alt/<%= alt_version %>/alt.min.js"></script>
  <script src="//cdn.jsdelivr.net/lodash/<%= lodash_version %>/lodash.min.js"></script>
  <%= csrf_meta_tags %>
</head>
<body>
<%= render 'shared/nav' %>
<%= yield %>
</body>
</html>
          

We will create a new directory under views called shared for all the partials, we'll also create a directory for all the todos templates. The view templates are as follows


<!-- views/shared/_nav.html.erb -->
<div class="navbar navbar-inverse" role="navigation">
  <div class="container">
    <a class="navbar-brand" href="#">Todos with React & Alt</a>
    <a class="navbar-right navbar-img" href="#"><%= image_tag 'react.png' %></a>
  </div>
</div>
          

# views/todos/_todo.json.jbuilder
json.extract!(todo, :id, :name, :completed)

# views/todos/index.json.jbulder
json.todos @todos, partial: 'todos/todo', as: :todo

# view/todos/index.html.erb
<%= react_component('TodoIndex', render(template: 'todos/index.json.jbuilder')) %>
          

If you start the rails server and open http://localhost:3000 you should see a blank page with the bootstrap navigation bar. We've got a lot done as far as setup goes, now let us start with our first react component. Before we do that let us seed our database so that we can list them using React.JS


# db/seeds.rb
Todo.create(name: 'Grocery shopping', completed: false)
Todo.create(name: 'Grooming', completed: true)
Todo.create(name: 'Cooking', completed: false)
Todo.create(name: 'Workout', completed: true)
          

Lets run the db seed and also save the alt.min.js locally so we can reference it within components.js manifest file.


rake db:seed
wget http://cdnjs.cloudflare.com/ajax/libs/alt/0.18.3/alt.min.js -O app/assets/javascripts/alt.min.js
          

Edit the app/assets/javascripts/components.js file and replace it with this


//= require alt.min
//= require initialize
//= require_tree ./react
          

Create a new file initialize.js.coffee under app/assets/javascripts as follows and rename app/assets/javascripts/components to react.


window.alt = new Alt()
          

With all this setup done, now we are in business. We'll start writing our React.JS components. The todo_actions.js.coffee will have a list of actions applicable for this app - let us start with just initData. The todo_store.js does all the server activities for us and we'll just start with a constructor and a couple of methods getTodos and onInitData. We'll cover props and state in depth in the second part of the blog.

todo_index.js.coffee will define the React component to create a


# app/assets/javascript/react/todo_actions.js.coffee
class TodoActions
  constructor: ->
    @generateActions(
      'initData'
    )

window.TodoActions = alt.createActions(TodoActions)

# app/assets/javascript/react/todo_store.js.coffee
class TodoStore
  @displayName: 'TodoStore'

  constructor: ->
    @bindActions(TodoActions)
    @todos = []

    @exportPublicMethods(
      {
        getTodos: @getTodos
      }
    )

  onInitData: (props) ->
    @todos = props.todos

  getTodos: () ->
    @getState().todos

window.TodoStore = alt.createStore(TodoStore)

# app/assets/javascript/react/todo_index.js.coffee
{ div, h1, ul, li, a, span, label, input, button, i  } = React.DOM

TodoListItem = React.createFactory React.createClass
  render: ->
    inputClassName = 'form-control'

    div className: 'input-group input-group-lg',
      span className: 'input-group-addon',
        input
          type: 'checkbox'
          checked: @props.todo.completed
      input
        type: 'text'
        value: @props.todo.name
        className: inputClassName
      span className: 'input-group-btn',
        button
          className: 'btn btn-danger'
          type: 'button',
            i
              className: 'glyphicon glyphicon-remove'

TodoList = React.createFactory React.createClass
  render: ->
    div className: 'todos',
      _.map @props.todos, (todo) =>
        TodoListItem(todo: todo)

window.TodoIndex = React.createClass
  getInitialState: ->
    todos: []

  componentWillMount: ->
    TodoStore.listen(@onChange)
    TodoActions.initData(@props)

  onChange: (state) ->
    @setState(state)

  render: ->
    div className: 'outer-container',
      TodoList(todos: @state.todos)
          

When you refresh http://localhost:3000 you should see a list of todos from the seed that we ran previously as shown below

This concludes part 1 and in the next part we'll see what props and state are in React and how Alt can help considerably with the help of store and actions.