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:
reactrb
for specific problems.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.
1 rails new hyperloop-showcase
2 cd hyperloop-showcase
3 bundle install
Update your Database config file for accessing your Database properly: config/database.yml
.
Then, execute:
1 rails db:create
You should have an empty Rails application:
1 bundle exec rails s
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.
There are two ways to achieve that:
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'
Then run:
1bundle install
Now let’s get the HyperRails generator to install Hyperloop :
1rails g hyperloop:install --all
2bundle update
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'
If you are interested in the steps the generator has completed, please see the following section.
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
Then run:
1bundle update
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'
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 itapplication.rb
. The example below assumes you are using Ruby syntax. However, if you are usingapplication.js
then use the standard//= require '...'
format and load your components withOpal.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');
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...
1#routes.rb
2
3mount HyperMesh::Engine => '/rr'
1#app/models/models.rb
2
3require_tree './public' if RUBY_ENGINE == 'opal'
Create a new folder: app/models/public
.
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?
Let's create the component via the generator:
1rails g hyperloop:component Home::Show
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
Add a route to your routes.rb
:
1#routes.rb
2
3root 'home#show'
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
Fire up the server with:
1bundle exec rails s
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;
Remember this value, as we will need to use it later.
There are five steps to this process:
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
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};
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};
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");
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");
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]
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.
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'
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'
And remove the following line:
1#app/views/components.rb
2
3require 'react/react-source'
This step is really simple because we have NPM installed. Just run these two commands to install React:
1npm install react --save
To install ReactDOM:
1npm install react-dom --save
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");
And finally, we need to run webpack
to add them to the bundle:
1webpack
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
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]
Congratulations! You are ready to begin adding Javascript packages to your application.
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.
First install the component via NPM:
1npm install react-player --save
Next, we need to require
it in webpack/client_and_server.js
1//webpack/client_and_server.js
2
3ReactPlayer = require("react-player");
Next, run Webpack so it can be bundled:
1webpack
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
Refresh your browser and you should have a video. How simple was that!
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");
Run the
1webpack
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
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
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};
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};
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");
And install the Bootstrap package:
1npm install bootstrap --save
Now run
1webpack
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
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!
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");
Then run:
1webpack
Restart your Rails app and refresh your browser.
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.
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
Now run the migration:
1rails db:migrate
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/
.
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
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
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.
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
Restart your server, open two browser windows and be amazed to see any new planevents added to one session magically appearing in the other!
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'
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
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
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 }
Save and you should see the button text change magically in your browser without having to refresh. Pure joy.
localhost:5000
instead of localhost:3000
.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.
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.