Skip to content
This repository has been archived by the owner on Oct 19, 2018. It is now read-only.

Component Anatomy

Mitch VanDuyn edited this page Jan 24, 2017 · 8 revisions

React Components in Ruby!

At the core of a Hyperloop application are components. These are your views, and instead of being written in ERB, HAML, JSX, Mustache, Dirty Underwear or some other templating language, they are written as Ruby classes which build React components.

For example here is a simple component that displays a user with their avatar and a link to their website:

class User < React::Component::Base
  param :name
  param :avatar
  param :website

  def url(s)
    s =~ /^http:/ ? s : "http://#{s}"
  end

  render(DIV) do 
    IMG(src: url(params.avatar))
    A(class: :username, target: :_blank, href: url(params.website)) { params.name }
  end
end

To display a user we would say:

  User(name: "Hyperloop", avatar: "goo.gl/epMF6e", website: "ruby-hyperloop.io")

The DSL defines methods like DIV, IMG, and A for all the HTML tags, and then your app defines other components (like User) and helper methods (like url.) You put this all together in a render block which can use if, each and any other Ruby construct to build your HTML.

Note that its easy to read the DSL: All uppercase names are built-in tags, CamelCase names are application defined components, and snake_case names are methods and ruby constructs.

Displaying Stores

Using a Store in your component is simple Ruby code. We will use our WorldClock store in a couple of components to display the current time for London and New York.

class DisplayTime < React::Component::Base
  param :clock
  param :format # time format string
  render(DIV) do
    "The time in #{params.clock.name} is "\
    "#{params.clock.current_time % params.format}"
  end
end

class App < React::Component::Base

  FORMATS = {
    12 => "%a, %e %b %Y %l:%M:%S %p", 
    24 => "%a, %e %b %Y %H:%M:%S"
  }

  CLOCKS = [
    WorldClock.new('New York', 40.7128, -74.0059, 5.hours), 
    WorldClock.new('London', 51.5074, -0.1278, 0)
  ]

  render(DIV) do
    CLOCKS.each do |clock|
      DisplayTime format: FORMATS[12], clock: clock
    end
  end
end

Our component is very happy because we have full access to all of Ruby's great style. We put our clocks into a constant array and then iterate through that array. We are not sure whether we should display the clock in 12 or 24-hour time, so we set up both formats and for now hard code our app to use the 12-hour (AM/PM) format.

Binding Input Events

It is easy to bind user input events in Components. We will provide a button to change the time format from 12 to 24-hour clock.

We want to present a SELECT drop down with the choices 12 and 24. When the user changes the SELECT we want to change the format.

Any time we respond to a user event we are going to change state somewhere in the system. For now, we are going to keep a private state called format in our App component. Like Stores, Components can also declare and use state variables. Like any state variable, as it changes any components depending on the current state of the variable will be updated.

class App < React::Component::Base

  FORMATS = {
    12 => "%a, %e %b %Y %I:%M:%S %p", 
    24 => "%a, %e %b %Y %H:%M:%S"
  }

  CLOCKS = [
    WorldClock.new('New York', 40.7128, -74.0059, 5.hours), 
    WorldClock.new('London', 51.5074, -0.1278, 0)
  ]

  private_state format: FORMATS[12] # initial format to 12-hour

  render(DIV) do
    CLOCKS.each do |clock|
      DisplayTime format: state.format, clock: clock
    end
    SELECT do
      FORMATS.each { |name, format| OPTION(value: format) { value } }
    end.on(:change) { |e| state.format! = e.value }
  end
end

Whenever the SELECT changes the

{ |e| state.format! = e.value }

block is run. This updates the format state variable which will cause a re-render of both clock components.

Again the key to understanding user events is that they always update state.

Clone this wiki locally