You’ve all heard of React. It’s no longer the new kid on the block and has achieved enough of a foothold in the volatile Javascript ecosystem that we’re not concerned about learning something that will be redundant in 6 months. React is based on some very good ideas; It replaces the View in the Model-View-Controller pattern with a virtual Document Object Model and a blazingly fast diffing algorithm that ensures the only updates to the page are the changes you have made. The only downside is having to write JSX or Javascript! Fortunately, we can get around that with some Ruby APIs designed to solve exactly that problem.
Is React Right For Your App?
React shines when used with Single Page Apps or in any instance where partial-page reload is beneficial. Rails has had Turbolinks (think PJAX if you’re not familiar) out of the box since 4.0, so if you’re looking for speed via partial re-render, you’re likely better off with that. React also doesn’t offer you any AJAX functionality, a data layer, or any of the other things we take for granted in modern web development. So, if you think of it as drop-in replacement for Rails, you’re going to have a bad time.
Also, since it’s predominantly designed for dynamic page re-rendering, it’s going to be a large investment for small return if you have a mostly static site. Oh, and if you’re supporting browsers lower than IE9, then this is not for you because React doesn’t work with them and never will. In short and like anything, if you don’t play to the strengths of the tool you are using, then you’re probably better off not using it.
With that out of the way, let’s get started!
Integration
You want to integrate this into your Rails project? No problem! The Rails ecosystem has several methods of integration including WebPack, Gulp, and a few plugins (react-rails appears to be leading the pack at the time of writing) that do exactly that. What if you’re not using Rails? What if you prefer the lightness afforded to you by a micro-framework like Cuba or Sinatra? Well, then we have a little more work to do.
First, start a new skeleton project. We’ll use Cuba for this, but it will work equally well with Sinatra (with some slight modifications to your config.ru). For quick and dirty projects, I use the excellent Cuba Genie, which bundles Bootstrap, Cuba, and a bunch of nice touches into a simple project.
Assuming you have a simple project working, here’s how you integrate React:
Add the following Gems to your Gemfile:
gem "opal-jquery"
gem "reactive-ruby"
and run bundle install
First, create some simple HTML templates. I’m using mote:
<!-- views/layout.mote -->
<!doctype html>
<html>
<head>
<script src="/assets/react.js"></script>
<script>{{ app.loader }}</script>
</head>
<body>
<div id="layout">
{{ content }}
</div>
</body>
</html>
<!-- home.mote -->
<div id='content'></div>
There’s a couple of things to talk about here: First off, the inclusion of <div id="content">
. Those of you who’ve glanced at the React tutorial might recall that React needs a mount point on the DOM. This is that.
/assets/react.js – This file doesn’t exist on our file system; it is transpiled at runtime from react.rb, which we will look at in a moment.
{{ app.loader }}
will translate to this string:
if (typeof(Opal) !== 'undefined') {
Opal.mark_as_loaded("opal");
Opal.mark_as_loaded("corelib/runtime.self");
Opal.mark_as_loaded("jquery.self");
Opal.mark_as_loaded("sources/react.self");
Opal.load("react");
}
This is handling loading third-party libraries, including React and jQuery while allowing the transpiler to serve the JS generated from our Ruby code. It doesn’t work without this string.
Now let’s create a simple React class. In spite of the file type, this will be located in your js directory:
# react.rb:
require 'opal'
require 'jquery'
require 'opal-jquery'
require 'reactive-ruby'
class HelloWorld
include React::Component
def render
h1 {"Hello, World!"}
end
end
This is a simple div
with the h1
text, Hello, World
. It is the equivalent of the following JSX:
ReactDOM.render(<h1>Hello World</h1>, document.getElementById('content'));
We’ll use jQuery’s document.ready
event to mount React. This necessitates adding jQuery to your project:
Document.ready? do
React.render(React.create_element(HelloWorld), Element["#content"])
end
Next, we need to add the loader method to our Cuba instance. This can be done by adding this code to your Cuba class:
# app.rb
require 'opal'
def loader
opal = Opal::Server.new {|s|
s.append_path 'js'
s.main = 'react'
}
Opal::Processor.load_asset_code(opal.sprockets, 'react')
end
Finally, none of this will work without a few modifications to our config.ru: we need to serve our assets, set some template variables, and gear our app up for transpiling. Your config.ru should look like this:
# config.ru
require 'bundler'
require_relative 'app'
Bundler.require
opal = Opal::Server.new {|s|
s.append_path 'js'
s.main = 'app'
}
sprockets = opal.sprockets
maps_prefix = '/__OPAL_SOURCE_MAPS__'
maps_app = Opal::SourceMapServer.new(sprockets, maps_prefix)
# Monkeypatch sourcemap header support into sprockets
::Opal::Sprockets::SourceMapHeaderPatch.inject!(maps_prefix)
map maps_prefix do
run maps_app
end
map '/assets' do
run sprockets
end
run(Cuba)
This starts an Opal server, sets the entry points for the transpiler, injects header support into sprockets, and serves all of this from the assets directory.
The moment of truth: navigate to 127.0.0.1:9292
to see:
`Hello, World`
Congratulations; you are now running the cutting edge of Javascript technology in a microframework!
Notes
This is a surprisingly large amount of work to integrate something into your project. We’ve had to configure transpilers, set mount points, define entry points, write HTML. and all for “Hello World!”. I quickly tired of this and wrote a Gem, called cuba_react to do all of this for you. I would advocate using the gem rather than spending hours in Pry’s debug console fervently trying to ascertain for what reason your transpiler isn’t seeing source files or not returning what you think it is.
More importantly, the gem will automate all of this and allow you to skip the boring stuff and just get started playing with React! Add cuba_react
to your Gemfile, bundle install
, and leverage the inbuilt CLI to generate all of these files for you with cuba_react generate
. Documentation can be found here.
Extending Our Example
Naturally, “Hello, World!” is just about useless in the real world, so you should extend our example a little. Check out the excellent reactrb docs for some examples of what’s possible, but here’s a simple login class:
class Nav < React::Component::Base
def render
div do
input(class: :handle, type: :text, placeholder: "Enter Your Handle")
button(type: :button) { "login!" }.on(:click) do
alert("#{Element['input.handle'].value} logs in!")
end
end
end
end
As for next steps, I would suggest checking out the official React getting started tutorial, which isn’t the best documentation I’ve ever seen, but will certainly get you familiar with some concepts. The examples are all in JSX, so it is an exercise left to the reader to translate them to Ruby, but it should be fairly straightforward and a gentle primer to React. After that, I would recommend react for beginners which is an excellent series of videos explaining the concepts behind the framework and a walkthrough detailing how to build a relatively complex app. There are some excellent egghead.io React videos too.
I hope this has been a rewarding exercise and you are now better placed to put React into a Cuba project. Happy coding!
David Bush is a Web Developer who travels the world and writes code. His favourite languages are Clojure, Ruby and Python. He enjoys learning new technologies, beer, good food and trying new things. www.david-bush.co.uk