Author avatar

Barrie Hadfield

Reactrb Showcase

Barrie Hadfield

  • May 11, 2019
  • 38 Min read
  • 2,170 Views
  • May 11, 2019
  • 38 Min read
  • 2,170 Views
Web Development
React

HyperLoop Showcase

This is a simple Rails application showcasing HyperReact (a wrapper for the React.js library for creating user interfaces), Opal, NPM, Webpack, React-Bootstrap, HyperMesh (gives your HyperReact components CRUD access to your server-side ActiveRecord models and implements push notifications), and other associated technologies.

This Showcase application will mix native React and HyperReact components, be styled by Bootstrap CSS (using React-Bootstrap), display a video (using a native React component), and use HyperMesh to handle data for an Events feed app.

The Showcase application will look like this:

Screen

Technologies Highlighted in This Showcase Application

Introduction

Introductions to HyperReact

HyperReact Help and Questions

Using NPM and Webpack Alongside Rails

Ruby libraries are distributed as gems, and are managed in your Rails app using the Gemfile and bundler.

In the JavaScript world things are still evolving but I have found that the easiest way to manage JavaScript libraries is by using NPM (Node Package Manager) and Webpack. Pretty much every front end library is packaged with NPM these days so it is easy to get help and most things just work.

Happily NPM, Webpack, Rails, and HyperReact can all play together very nicely.

This guide requires that Ruby, Rails, NPM, and Webpack are installed. Please see their websites for installation instructions.

Step 1: Creating a New Rails Application

1	rails new hyperloop-showcase
2	cd hyperloop-showcase
3	bundle install
bash

Update your Database config file for accessing your Database properly: config/database.yml.

Then, execute:

1	rails db:create
bash

You should have an empty Rails application:

1	bundle exec rails s
bash

And in your browser:

1	http://localhost:3000/

You should see the Rails Welcome aboard page. Great, Rails is now installed. Let's get started with the interesting stuff.

Step 2: Adding HyperReact and HyperMesh

There are two ways to achieve that:

  • Using HyperLoop installation generator
  • Manually

Automatic Installation of HyperReact and HyperMesh

Add the HyperRails gem, which is a set of generators which will easily configure the other Hyperloop gems :

1  #Gemfile
2
3  gem 'hyper-rails'
ruby

Then run:

1bundle install
bash

Now let’s get the HyperRails generator to install Hyperloop :

1rails g hyperloop:install --all
2bundle update
bash

HyperRails will add all the necessary Gem’s and configuration to our new Rails app.

We still need to modify the app/views/components.rb and add the two lines require 'reactrb/auto-import' and require 'react/react-source' :

1#app/views/components.rb
2
3require 'opal'
4
5require 'reactrb/auto-import'
6require 'react/react-source'
7require 'hyper-react'
8if React::IsomorphicHelpers.on_opal_client?
9  require 'opal-jquery'
10  require 'browser'
11  require 'browser/interval'
12  require 'browser/delay'
13  # add any additional requires that can ONLY run on client here
14end
15
16require 'hyper-mesh'
17require 'models'
18
19require_tree './components'
ruby

If you are interested in the steps the generator has completed, please see the following section.

Manual Installation of HyperReact and HyperMesh

Step 2.1: Add the Gems

In your Gemfile

1  #Gemfile
2
3	gem 'react-rails', '1.4.2'
4	gem 'hyper-rails', '0.4.1'
5	gem 'opal-rails', '0.9.1'
6	gem 'opal-browser', '0.2.0'
7	gem 'hyper-react', '0.11.0'
8	gem 'hyper-mesh', '0.5.3'
9	gem 'hyper-router', '2.4.0'
10	gem 'therubyracer', platforms: :ruby
ruby

Then run:

1bundle update
Step 2.2: Add the Components Directory and Manifest

Your React components will go into the app/views/components/ directory of your rails app.

Within your app/views directory you need to create a components.rb manifest.

Files required in app/views/components.rb will be made available to the server side rendering system as well as the browser.

1#app/views/components.rb
2
3require 'opal'
4
5require 'reactrb/auto-import'
6require 'react/react-source'
7require 'hyper-react'
8if React::IsomorphicHelpers.on_opal_client?
9  require 'opal-jquery'
10  require 'browser'
11  require 'browser/interval'
12  require 'browser/delay'
13  # add any additional requires that can ONLY run on client here
14end
15
16require 'hyper-mesh'
17require 'models'
18
19require_tree './components'
ruby
Step 2.3: Client Side Assets

Typically, the client will need all the above assets plus other files that are client only. Notably jQuery is a client-only asset.

NOTE: You can update your existing application.js file, or convert it to Ruby syntax, and name it application.rb. The example below assumes you are using Ruby syntax. However, if you are using application.js then use the standard //= require '...' format and load your components with Opal.load('components');.

Assuming you are using the Ruby syntax (application.rb), in assets/javascript/application.rb require your components manifest as well as any additional browser only assets.

1#assets/javascript/application.js
2
3//= require 'components'
4//= require 'react_ujs'
5//= require 'jquery'
6//= require 'jquery_ujs'
7//= require 'turbolinks'
8//= require_tree .
9
10Opal.load('components');
ruby
Step 2.4: Update application.rb

Finally, you will need to update your application.rb to ensure everything works in production:

1#config/application.rb
2...
3  class Application < Rails::Application
4    # Settings in config/environments/* take precedence over those specified here.
5    # Application configuration should go into files in config/initializers
6    # -- all .rb files in that directory are automatically loaded.
7    config.eager_load_paths += %W(#{config.root}/app/models/public)
8    config.autoload_paths += %W(#{config.root}/app/models/public)
9    config.assets.paths << ::Rails.root.join('app', 'models').to_s
10  end
11...
ruby
Step 2.5: Update routes.rb
1#routes.rb
2
3mount HyperMesh::Engine => '/rr'
ruby
Step 2.6: Create app/models/models.rb
1#app/models/models.rb
2
3require_tree './public' if RUBY_ENGINE == 'opal'
ruby
Step 2.7: Create app/models/public

Create a new folder: app/models/public.

Step 2.8: Create app/policies/application_policy.rb
1	#app/policies/application_policy.rb
2
3	# Policies regulate access to your public models
4	# The following policy will open up full access (but only in development)
5	# The policy system is very flexible and powerful.  See the documentation
6	# for complete details.
7	class ApplicationPolicy
8	  # Allow any session to connect:
9	  always_allow_connection
10	  # Send all attributes from all public models
11	  regulate_all_broadcasts { |policy| policy.send_all }
12	  # Allow all changes to public models
13	  allow_change(to: :all, on: [:create, :update, :destroy]) { true }
14	end if Rails.env.development?
ruby

Step 3: Creating the First Components

Let's create the component via the generator:

1rails g hyperloop:component Home::Show
ruby

This will add a new Component at app/views/components/home/show.rb

1#app/views/components/home/show.rb
2
3module Components
4  module Home
5    class Show < React::Component::Base
6
7      # param :my_param
8      # param param_with_default: "default value"
9      # param :param_with_default2, default: "default value" # alternative syntax
10      # param :param_with_type, type: Hash
11      # param :array_of_hashes, type: [Hash]
12      # collect_all_other_params_as :attributes  #collects all other params into a hash
13
14      # The following are the most common lifecycle call backs,
15      # the following are the most common lifecycle call backs# delete any that you are not using.
16      # call backs may also reference an instance method i.e. before_mount :my_method
17
18      before_mount do
19        # any initialization particularly of state variables goes here.
20        # this will execute on server (prerendering) and client.
21      end
22
23      after_mount do
24        # any client only post rendering initialization goes here.
25        # i.e. start timers, HTTP requests, and low level jquery operations etc.
26      end
27
28      before_update do
29        # called whenever a component will be re-rerendered
30      end
31
32      before_unmount do
33        # cleanup any thing (i.e. timers) before component is destroyed
34      end
35
36      def render
37        div do
38          "Home::Show"
39        end
40      end
41    end
42  end
43end
ruby

Add a route to your routes.rb:

1#routes.rb
2
3root 'home#show'
ruby

Create the file app/controllers/home_controller.rb and add a show method. This will render the component using the render_component helper.

1#app/controllers/home_controller.rb
2
3class HomeController < ApplicationController
4    def show
5		render_component
6    end
7end
ruby

Fire up the server with:

1bundle exec rails s
ruby

Refresh your browser and, if all has gone well, you should be rewarded with Home::Show in your browser. If you open your JavaScript console, you can also check which version of React has been loaded.

1React.version;
javascript

Remember this value, as we will need to use it later.

Step 4: Managing Front End Assets with Webpack

There are five steps to this process:

  • Adding Webpack to your Rails project
  • Setting up Webpack
  • Using Webpack to build your client and server bundles
  • Installing React and ReactDOM via NPM
  • Adding Webpack bundles to the Rails asset pipeline
Step 4.1: Adding Webpack to Your Rails Project

Run these three commands:

1npm init

Press enter at each prompt to leave the fields empty. Agree when asked if it is okay to write the package.json file. This will create an empty package.json (which is similar to a Gemfile) in your root folder.

1npm install webpack --save-dev

This installs Webpack and creates a node_modules folder. This folder contains hundreds of JavaScript dependencies.

1npm install webpack -g

This enables us to run Webpack from the command line.

Our project does not need all the dependencies in the node_modules folder, so let’s tell git to ignore them by adding a .gitignore file:

1#/.gitignore
2
3/node_modules
Step 4.2: Setting up Webpack

Now that we have Webpack, we need to add three boilerplate files to configure it. As you add more JavaScript packages, you will be updating these files. Again, this is similar to updating your Gemfile when you add new gems to a project. Run npm view webpack version if you are unsure which version you have. Add webpack.config.js to the root of your project:

For Webpack 1.x versions:

1// webpack.config.js
2
3var path = require("path");
4
5module.exports = {
6  context: __dirname,
7  entry: {
8    client_only: "./webpack/client_only.js",
9    client_and_server: "./webpack/client_and_server.js"
10  },
11  output: {
12    path: path.join(__dirname, "app", "assets", "javascripts", "webpack"),
13    filename: "[name].js",
14    publicPath: "/webpack/"
15  },
16  module: {
17    loaders: [
18      // add any loaders here
19    ]
20  },
21  resolve: {
22    root: path.join(__dirname, "..", "webpack")
23  }
24};
javascript

For Webpack 2.x versions:

1// webpack.config.js
2
3var path = require("path");
4
5module.exports = {
6  context: __dirname,
7  entry: {
8    client_only: "./webpack/client_only.js",
9    client_and_server: "./webpack/client_and_server.js"
10  },
11  output: {
12    path: path.join(__dirname, "app", "assets", "javascripts", "webpack"),
13    filename: "[name].js",
14    publicPath: "/webpack/"
15  },
16  module: {
17    loaders: [
18      // add any loaders here
19    ]
20  },
21  resolve: {
22    modules: [path.join(__dirname, "src"), "node_modules"]
23  }
24};
javascript

Then create a folder called webpack in your application root directory and add the following two files:

1// webpack/client_only.js
2
3// any packages that depend specifically on the DOM go here
4// for example the Webpack CSS loader generates code that will break prerendering
5console.log("client_only.js loaded");
javascript
1// webpack/client_and_server.js
2
3// all other packages that you can run on both server (prerendering) and client go here
4// most well behaved packages can be required here
5console.log("client_and_server.js loaded");
javascript
Step 4.3: Using Webpack to Build Your Client and Server Bundles

Simply run this command:

1webpack

You should see a result something like this:

1Hash: 756a1dc4a11c8fccd0a4
2Version: webpack 1.14.0
3Time: 55ms
4               Asset     Size  Chunks             Chunk Names
5client_and_server.js  1.61 kB       0  [emitted]  client_and_server
6      client_only.js  1.61 kB       1  [emitted]  client_only
7   [0] ./webpack/client_and_server.js 214 bytes {0} [built]
8   [0] ./webpack/client_only.js 206 bytes {1} [built]
bash

Our client_and_server.js and client_only.js bundles are built and ready to be included in our application. If you look in your app/assets/javascripts/webpack folder you should see the two files there. Note that these bundles are empty at the moment, as we have not added any JavaScript components yet. We will do that in another step.

Step 4.4: Adding Webpack Bundles to the Rails Asset Pipeline

Finally we need to require these two bundles in our rails asset pipeline. Edit app/assets/javascripts/application.js and add:

1// app/assets/javascripts/application.js
2
3//= require 'webpack/client_only'
javascript

Then edit app/views/components.rb and directly after require 'hyper-react' add the following two lines:

1#app/views/components.rb
2
3require 'webpack/client_and_server.js'
ruby

And remove the following line:

1#app/views/components.rb
2
3require 'react/react-source'
ruby
Step 4.5: Installing React and ReactDOM

This step is really simple because we have NPM installed. Just run these two commands to install React:

1npm install react --save
bash

To install ReactDOM:

1npm install react-dom --save
bash

Note how this modifies your package.json and installs React and ReactDOM in your node_modules folder.

Next, we need to require them into client_and_server.js:

1// webpack/client_and_server.js
2
3ReactDOM = require("react-dom");
4React = require("react");
javascript

And finally, we need to run webpack to add them to the bundle:

1webpack
bash

The output should look something like this:

1Hash: db338e21dcc44f66e5b5
2Version: webpack 1.14.0
3Time: 737ms
4               Asset     Size  Chunks             Chunk Names
5client_and_server.js   740 kB       0  [emitted]  client_and_server
6      client_only.js  1.61 kB       1  [emitted]  client_only
7   [0] ./webpack/client_and_server.js 271 bytes {0} [built]
8   [0] ./webpack/client_only.js 206 bytes {1} [built]
9    + 177 hidden modules
bash

Note that client_and_server.js has gone from 1.61kB to 740kB because it now includes the source of React and ReactDOM.

Now run bundle exec rails s and refresh the browser. Look at the console and you should see something like this:

1client_and_server.js loaded
2client_only.js loaded
3client_and_server.js loaded
4************************ React Prerendering Context Initialized Show ***********************
5************************ React Browser Context Initialized ****************************
6Reactive record prerendered data being loaded: [Object]
bash

Congratulations! You are ready to begin adding Javascript packages to your application.

Step 5: Working with Native React Components

Now we can reap the rewards of all the hard work above. We have everything setup to easily add front-end components and work with them in HyperReact. Let's jump in and add a native React component that plays a video.

Step 5.1: Adding a Video Player to Your Site

First install the component via NPM:

1npm install react-player --save
text

Next, we need to require it in webpack/client_and_server.js

1//webpack/client_and_server.js
2
3ReactPlayer = require("react-player");
javascript

Next, run Webpack so it can be bundled:

1webpack
text

And then finally add it to the Show component:

1#app/views/components/home/show.rb
2
3def render
4  div do
5    ReactPlayer(url:  'https://www.youtube.com/embed/FzCsDVfPQqk',
6      playing: true
7    )
8  end
9end
ruby

Refresh your browser and you should have a video. How simple was that!

Step 5.2: Working with React-Bootstrap

The main purpose for React-Bootstrap is that it abstracts away verbose HTML and CSS code into React components. This makes it a lot cleaner for React JSX developers. One of the very lovely things about Hyperloop is that we already work in beautiful Ruby. To emphasize this point, consider the following:

Sample 1 - In HTML (Without React-Bootstrap):

1    <button id="something-btn" type="button" class="btn btn-success btn-sm">
2      Something
3    </button>
4    $('#something-btn').click(someCallback);

Sample 2 - In JSX (With React-Bootstrap Components):

1    <Button bsStyle="success" bsSize="small" onClick={someCallback}>
2      Something
3    </Button>

Sample 3 - In Hyperloop (Without React-Bootstrap):

1    button.btn_success.btn_sm {'Something'}.on(:click) do
2    	someMethod
3    end

Sample 4 - In Hyperloop (With React-Bootstrap):

1    React-Bootstrap::Button(bsStyle: 'success' bsSize: "small") {'Something'}.on(:click) do
2    	someMethod
3    end

As you can see, sample 3 and 4 are not that different. As a Hyperloop developer, I actually prefer sample 3, but if I were a JavaScript or JSX developer, I would completely understand the advantage of abstracting Bootstrap CSS into React Components so I don't have to work directly with CSS and JavaScript. This is not the case with HyperReact, however, as CSS classes are added to HTML elements with simple dot notation:

1    span.pull_right {}

Which compiles to (note the conversion from _ to -):

1    <span class='pull-right'></span>

So I hear you ask: Why, if I prefer the non-React-Bootstrap syntax, am I worrying about React-Bootstrap? For one very simple reason: components like Navbar and Modal that require bootstrap.js will not work with React on its own, so without the React-Bootstrap project you would need to implement all that functionality yourself. The React-Bootstrap project has re-implemented this functionality as React components.

Let's implement a Navbar in this project using React-Bootstrap in HyperReact. First, we need to install Bootstrap and React-Bootstrap:

1    npm install bootstrap react-bootstrap --save

Note: The --save option will update the package.json file.

And then we need to require it in webpack/client_and_server.js by adding this line:

1//webpack/client_and_server.js
2
3ReactBootstrap = require("react-bootstrap");
javascript

Run the

1webpack
text

command again, and restart your rails server.

If you refresh your browser now and open the JavaScript console, we will be able to interact with React-Bootstrap by typing in the JavaScript console: ReactBootstrap.

You will see the React-Bootstrap object with all of its components like Accordion, Alert, Badge, Breadcrumb, etc. This is great news. React-Bootstrap is installed and ready to use. Accessing the JavaScript object in this way is a great way to see what you have to work with. Sometimes the documentation of a component is not as accurate as actually seeing what you have in the component itself.

To make sure everything is working, let's add a Button to our our Show component like this:

1#app/views/components/home/show.rb
2
3module Components
4  module Home
5    class Show < React::Component::Base
6      def render
7        ReactBootstrap::Button(bsStyle: 'success', bsSize: "small") do
8          'Success'
9        end.on(:click) do
10          alert('you clicked me!')
11        end
12      end
13    end
14  end
15end
ruby

Notice that we reference ReactBoostrap in Ruby using the same identifier that was in the require statement in our client_and_server.js Webpack bundle. The first time HyperReact hits the ReactBootstrap constant it will not be defined. This triggers a search of the Javascript name space for something that looks either like a component or library of components. It then defines the appropriate module or component class wrapper in Ruby.

Visit your page and, if all is well, you will see a clickable button. However it will not have any style. This is because React-Bootstrap does not automatically depend on any particular style sheet, so we will have to supply one. An easy way to do this is to just copy the CSS file from the Bootstrap repo and stuff it into our rails assets directory. However, with a little upfront work, we can get Webpack to do it all for us.

First let's add four Webpack loaders using npm:

1npm install css-loader file-loader style-loader url-loader --save-dev
text

Notice we use --save-dev instead of just --save, as these packages are only used in the development process.

Now, edit your webpack.config.js file and update the loaders section so it looks like this:

For Webpack 1.x versions

1//webpack.config.js
2
3var path = require("path");
4
5module.exports = {
6...
7    module: {
8      loaders: [
9        { test: /\.css$/,
10          loader: "style-loader!css-loader"
11        },
12        { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
13          loader: 'url?limit=10000&mimetype=application/font-woff'
14        },
15        { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
16          loader: 'url?limit=10000&mimetype=application/octet-stream'
17        },
18        { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
19          loader: 'file'
20        },
21        { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
22          loader: 'url?limit=10000&mimetype=image/svg+xml'
23        }
24      ]
25    },
26...
27};
javascript

For Webpack 2.x versions

1//webpack.config.js
2
3var path = require("path");
4
5module.exports = {
6...
7    module: {
8      rules: [
9      { test: /\.css$/,
10        use: [
11          {
12            loader: "style-loader"
13          },
14          {
15            loader: "css-loader"
16          }
17        ]
18      },
19      { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
20        loader: 'url-loader?limit=10000&mimetype=application/font-woff'
21      },
22      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
23        loader: 'url-loader?limit=10000&mimetype=application/octet-stream'
24      },
25      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
26        loader: 'file-loader'
27      },
28      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
29        loader: 'url-loader?limit=10000&mimetype=image/svg+xml'
30      }
31    ]
32    },
33...
34};
javascript

We have setup Webpack to use the style loader for processing a CSS file when one is required. Because the Bootstrap CSS file will require font face files, we also have four font loaders. Now, when any CSS file is required, we will have everything we need neatly packaged up.

We are now ready to require CSS files and have Webpack build a complete bundle including the CSS and any fonts referenced.

To bundle in the Bootstrap CSS file add this line to webpack/client_only.js

1//webpack/client_only.js
2
3require("bootstrap/dist/css/bootstrap.css");
javascript

And install the Bootstrap package:

1npm install bootstrap --save
text

Now run

1webpack
text

to update our bundles and restart your server. Our button is now properly styled and you should be rewarded with a nice Bootstrap-styled green Success Button.

Now that everything is loaded, let's update our component to use a few more of the Bootstrap components. Update your Show component so that it looks like this:

1#app/views/components/home/show.rb
2
3module Components
4  module Home
5    class Show < React::Component::Base
6
7      def say_hello(i)
8        alert "Hello from number #{i}"
9      end
10
11      def render
12        div do
13          ReactBootstrap::Navbar(bsStyle: :inverse) do
14            ReactBootstrap::Nav() do
15              ReactBootstrap::NavbarBrand() do
16                a(href: '#') { 'Hyperloop Showcase' }
17              end
18              ReactBootstrap::NavDropdown(
19                eventKey: 1,
20                title: 'Things',
21                id: :drop_down
22              ) do
23                (1..5).each do |n|
24                  ReactBootstrap::MenuItem(href: '#',
25                    key: n,
26                    eventKey: "1.#{n}"
27                  ) do
28                    "Number #{n}"
29                  end.on(:click) { say_hello(n) }
30                end
31              end
32            end
33          end
34          div.container do
35            ReactPlayer(url: 'https://www.youtube.com/embed/FzCsDVfPQqk',
36              playing: true
37            )
38          end
39        end
40      end
41    end
42  end
43end
ruby

A few things to notice in the code above:

We add React-Bootstrap components simply by ReactBootstrap::Name where Name is the JavaScript component you want to render. All the components are documented in the React-Bootstrap documentation

See with div.container we are mixing in CSS style which will compile into <div class='container'>

Also notice how I have added an .on(:click) event handler to the MenuItem component while setting href: '#' as this will allow us to handle the event instead of navigating to a new page.

So far we have a very basic application which is looking OK and showing a video. Time to do something a little more interesting. Let's add Post and Comment functionality which will let us explore ReactiveRecord!

Step 5.3: Using a Bootstrap Theme

We are going to replace the Bootstrap original theme with the Bootswatch Superhero theme.

Install the bootswatch package with NPM:

1npm install bootswatch

Modify the webpack/client_only.js file:

1//webpack/client_only.js
2
3require("bootswatch/superhero/bootstrap.min.css");
javascript

Then run:

1webpack
text

Restart your Rails app and refresh your browser.

Step 6: Working with HyperMesh and ActiveRecord

HyperMesh uses a large subset of the ActiveRecord API modified only when necessary to accommodate the asynchronous nature of the client. You can access your ActiveRecord models just like you would in models, controllers, or in ERB or HAML view templates. As much as possible, HyperMesh follows the syntax and semantics of ActiveRecord.

Read more details about HyperMesh here: HyperMesh ActiveRecord.

Step 6.1: Creating the Models
1rails g model Planevent

And then, before you run the migration, let's flesh them out a little so they look like this:

1# db/migrate/..create_planevents.rb
2
3class CreatePlanevents < ActiveRecord::Migration[5.0]
4  def change
5    create_table :planevents do |t|
6      t.string :planeventtitle
7      t.text :description
8      t.timestamps
9    end
10  end
11end
ruby

Now run the migration:

1rails db:migrate
Step 6.2: Making Your Models Accessible to HyperMesh

HyperMesh needs to 'see' your models because a representation of them gets compiled into JavaScript along with your HyperReact components so they are accessible in your client-side code.

Move planevent.rb to models/public.

For Rails 5.x only, move app/models/application_record.rb to app/models/public/.

Step 6.3: Accessing Your Models in HyperReact Components

To get started, let's create a new component which will display a list of Events under the video:

1# views/components/home/show.rb
2
3div.container do
4  ReactPlayer(url: 'https://www.youtube.com/embed/FzCsDVfPQqk', playing: true)
5  br # line break
6  PlaneventsList()
7end
ruby

Note that to place a HyperReact component, you either need to include ( ) or { }, so planeventsList() or PlaneventsList { } would be valid but just PlaneventsList would not.

Next let's create the PlaneventsList component:

1#app/views/components/home/planeventslist.rb
2
3module Components
4  module Home
5    class PlaneventsList < React::Component::Base
6
7      define_state new_planevent: Hash.new { |h, k| h[k] = '' }
8
9      before_mount do
10        # note that this will lazy load posts
11        # and only the fields that are needed will be requested
12        @planevents = Planevent.all
13        @planevent_attributes = Hash[ 'planeventtitle' => 'Event Name', 'description' => 'Description']
14      end
15
16      def render
17        div.container do
18        	div.row do
19          		new_planevent
20          	end
21
22          	hr
23
24          	div.row do
25          		table_render
26          	end
27
28        end
29      end
30
31      def table_render
32
33          div.col_md_12 do
34            br
35            table(class: "table table-hover") do
36              thead do
37                tr do
38                  td.text_muted.small(width: '33%') { "NAME" }
39                  td.text_muted.small(width: '33%') { "DESCRIPTION" }
40                  td.text_muted.small(width: '33%') { "DATE" }
41                end
42              end
43              tbody do
44                @planevents.reverse.each do |planevent|
45                  PlaneventsListItem(planevent: planevent)
46                end
47              end
48            end
49          end
50
51      end
52
53      def new_planevent
54
55      	@planevent_attributes.each do |attribute, value|
56
57	        ReactBootstrap::FormGroup() do
58
59	        	ReactBootstrap::ControlLabel() do
60					value
61				end
62				ReactBootstrap::FormControl(
63					value: state.new_planevent[attribute],
64					type: :text,
65					).on(:change) { |e|
66						state.new_planevent![attribute] = e.target.value
67					}
68	        end
69	     end
70
71        ReactBootstrap::Button(bsStyle: :primary) do
72          "Create an new event"
73        end.on(:click) { save_new_planevent }
74
75      end
76
77      def save_new_planevent
78
79        Planevent.create(state.new_planevent) do |result|
80          # note that save is a promise so this code will only run after the save
81          # yet react will move onto the code after this (before the save happens)
82          alert "unable to save" unless result == true
83        end
84        state.new_planevent.clear
85
86      end
87    end
88
89    class PlaneventsListItem < React::Component::Base
90      param :planevent
91
92      def render
93
94      	tr do
95          td(width: '33%') { params.planevent.planeventtitle }
96          td(width: '33%') { params.planevent.description }
97          td(width: '33%') { params.planevent.created_at.to_s }
98        end
99
100      end
101
102    end
103  end
104end
ruby

Things to note in the code above:

Note that we fetch the ReactiveRecord Planevent collection in before_mount. Setting this here instead of in after_mount means that we do not need to worry about @planevents being nil as the collection will always contain at least one entry with the actual records being lazy loaded when needed.

Note how we are binding the state variable new_planevent to the FormControl and then setting its value based on the value being passed to the .on(:change) block. This is a standard React pattern.

Also see that we are saving the new planevent such that ReactiveRecord's save returns a promise. This means that the block after save is only evaluated when it returns, yet React would have moved on to the rest of the code.

Finally note that there is no code which checks to see if there are new planevents -- the list of Planevents remains magically up-to-date. Welcome to the wonderful world of HyperReact and HyperMesh!

Refresh your browser and you should have your Showcase app working.

Step 7: Working with HyperMesh and Push Notifications

The HyperMesh ReactiveRecord part is the data layer between one client and its server. HyperMesh also uses push notifications to push changed records to all connected ReactiveRecord clients.

You need to add an initializer config/initializers/hyper_mesh.rb

1#config/initializers/hyper_mesh.rb
2
3HyperMesh.configuration do |config|
4  config.transport = :simple_poller
5end
ruby

Restart your server, open two browser windows and be amazed to see any new planevents added to one session magically appearing in the other!

Step 8: Working with HyperReact Hot Reloader and Opal IRB

Before we go any further, let's install two fantastic tools written by Forrest Chang:

Opal Hot Reloader is for pure programmer joy (not having to reload the page to compile your source) and the Opal Console is incredibly useful to test how Ruby code compiles to JavaScript.

We are also going to add the Foreman gem to run our Rails server and the Hot Reloader service for us.

Add the following lines to your gemfile and run bundle:

1#gemfile
2
3gem 'opal_hot_reloader', git: 'https://github.com/fkchang/opal-hot-reloader.git'
4gem 'foreman'
ruby

bundle install

Modify your components.rb, adding the following lines inside the if statement so they only run on the client and not as part of the server pre-rendering process:

1#app/views/components.rb
2
3if React::IsomorphicHelpers.on_opal_client?
4  ...
5  require 'opal_hot_reloader'
6  OpalHotReloader.listen(25222, true)
7end
ruby

Then create a Procfile in your Rails app root so the Hot Reloader service will start whenever you start your server:

1#Procfile
2
3rails: bundle exec rails server -p 3000
4hotloader: opal-hot-reloader -p 25222 -d app/views/components
text

To start both servers:

bundle exec foreman start

Refresh your browser and edit your planeventslist.rb:

1ReactBootstrap::Button(bsStyle: :primary) do
2  "New Event"
3end.on(:click) { save_new_planevent }
ruby

Save and you should see the button text change magically in your browser without having to refresh. Pure joy.

  • Note that there is a bug in PUMA 3.7.0, so if you are using this version of PUMA, the app will load at localhost:5000 instead of localhost:3000.

Further Reading

Other HyperReact Tutorials and Examples

HyperReact Is Powered by React

HyperReact is powered by React HyperReact and friends are in most cases simple DSL Ruby wrappers to the underlying native JavaScript libraries and React Components. It is really important to have a solid grip on how these technologies work to complement your understanding of HyperReact. Most searches for help on Google will take you to examples written in JSX or ES6 JavaScript but you will learn over time to translate this into HyperReact equivalents. To make headway with HyperReact you do need a solid understanding of the underlying philosophy of React and its component based architecture. The 'Thinking in React' tutorial below is an excellent place to start.

Make sure you see the Flux Store Tutorial in HyperReact above for an example of how to communicate between grandparent and child components.

Opal under the Covers

HyperReact is a DSL wrapper of React which uses Opal to compile Ruby code to ES5 native JavaScript. If you have not used Opal before then you should at a minimum read the excellent guides as they will teach you enough to get you started with HyperReact.