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 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 web site:

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 displays the current time for London and New York.

class DisplayTime < React::Component::Base
  param :clock, type: WorldClock
  render(DIV) do
    "The time in #{params.clock.name} is #{params.clock.current_time}"
  end
end

class App < React::Component::Base
  render(DIV) do
    DisplayTime clock: WorldClock.new('New York', 40.7128, -74.0059, 5.hours)
    DisplayTime clock: WorldClock.new('London', 51.5074, -0.1278, 0)
  end
end

Binding Input Events

It is easy to bind user input events in Components. For example, let's allow the user to select from different cities and add those WorldClocks to the UI dynamically.

First, we will need a simple static class to return a sample list of locations:

class Location
  LOCATIONS = [
    {name: 'New York', lattitude: 40.7128, longitude: -74.0059, time_zone_offset: 5.hours},
    {name: 'London', lattitude: 51.5074, longitude: -0.1278, time_zone_offset: 0},
    {name: 'Mumbai', lattitude: 19.0760, longitude: 72.8777, time_zone_offset: 5.hours+30.minutes},
    {name: 'Bangkok', lattitude: 13.7563, longitude: 100.5018, time_zone_offset: 7.hours},
    {name: 'Sydney', lattitude: -33.8688, longitude: 151.2093, time_zone_offset: 11.hours},
    {name: 'San Francisco', lattitude: 37.7749, longitude: 122.4194, time_zone_offset: -8},
    {name: 'Buenos Aires', lattitude: -34.6037, longitude: -58.3816, time_zone_offset: -3}
  ]
  def self.all 
    LOCATIONS
  end
end

Now let's define our application:

class App < React::Component::Base

  before_mount do
    # runs when the component is first mounted
    @locations = Location.all
  end

  def add_clock(clock)
    @locations.delete(clock)
    WorldClock.new(
      @locations[:name], 
      @locations[:lattitude], @locations[:longitude], 
      @locations[:time_zone_offset]
    )
  end

  def build_select 
    SELECT do
      @locations.each do |location|
        OPTION(value: location) { location[:name] }
      end
    end.on(:change) { |e| add_clock e.value }
  end
    
  render(DIV)
    build_select unless @locations.empty?
    WorldClock.all.each { |clock| DisplayTime clock: clock }
  end
end

When we generate the SELECT we bind the change event to the block

{ |e| add_clock e.value }

which will remove the selected clock from the @locations list, and create a new clock. WorldClock's all method will be updated causing a re-render with the new clock being displayed.

Notice how are component is tangled up massaging the city data into a WorldClock. We will be able to abstract a lot of that work out to an Operation. You will often see event handlers invoking a single Operation that exists separate from both the component and the store.

Clone this wiki locally