React To The Future With Isomorphic Apps
Things often come full circle in software engineering. The web in particular started with servers delivering content down to the client. Recently, with the creation of modern web frameworks such as AngularJS and Ember, we’ve seen a push to render on the client and only use a server for an API. We’re now seeing a possible return or, rather, more of a combination of both architectures happening.
What Is React?
React is a JavaScript library for building user interfaces.
According to the official website. It is a way to create reusable front-end components. Plain and simple, that is the goal of React.
What Makes It Different?
React has quickly risen to immense popularity in the JavaScript community. There are a number of reasons for its success. One is that Facebook created it and uses it. This means that many developers at Facebook work with it, fixing bugs, suggesting features and so on.
Another reason for its quick popularity is that it’s different. It’s unlike AngularJS, Backbone.js, Ember, Knockout and pretty much any of the other popular MV* JavaScript frameworks that have come out during the JavaScript revolution in the last few years. Most of these other frameworks operate on the idea of two-way binding to the DOM and updating it based on events. They also all require the DOM to be present; so, when you’re working with one of these frameworks and you want any of your markup to be rendered on the server, you have to use something like PhantomJS.
Virtual DOM
React is often described as the “V” in an MVC application. But it does the V quite differently than other MV* frameworks. It’s different from things like Handlebars, Underscore templates and AngularJS templates. React operates on the concept of a “virtual DOM.” It maintains this virtual DOM in memory, and any time a change is made to the DOM, React does a quick diff of the changes, batches them all into one update and hits the actual DOM all at once.
This has huge ramifications. First and foremost, performance-wise, you’re not constantly doing DOM updates, as with many of the other JavaScript frameworks. The DOM is a huge bottleneck with front-end performance. The second ramification is that React can render on the server just as easily as it can on the client.
React exposes a method called React.renderToString()
. This method enables you to pass in a component, which in turn renders it and any child components it uses, and simply returns a string. You can then take that string of HTML and simply send it down to the client.
Example
These components are built with a syntax called JSX. At first, JSX looks like a weird HTML-JavaScript hybrid:
var HelloWorld = React.createClass({
displayName: "HelloWorld",
render() {
return (
<h1>Hello {this.props.message}</h1>
);
}
});
React.render(<HelloWorld message="world" />, document.body);
What you do with this .jsx
format is pass it through (or “transpile”) webpack
, grunt
, gulp
, or your “renderer” of choice and then spit out JavaScript that looks like this:
var HelloWorld = React.createClass({
displayName: "HelloWorld",
render: function() {
return (
React.createElement("h1", null, "Hello ", this.props.message)
);
}
});
React.render(React.createElement(HelloWorld, {message: "world"}), document.body);
That’s what our HelloWorld.jsx
component transpiles to — nothing more than simple JavaScript. Some would consider this a violation of the separation of concerns by mixing JavaScript with HTML. At first, this seems like exactly what we’re doing. However, after working with React for a while, you realize that the close proximity of your component’s markup to the JavaScript enables you to develop more quickly and to maintain it longer because you’re not jumping back and forth between HTML and JavaScript files. All the code for a given component lives in one place.
React.render
attaches your <HelloWorld>
component to the body
. Naturally, that could be any element there. This causes the component’s render
method to fire, and the result is added to the DOM inside the <body>
tag.
With a React component, whatever you pass in as attributes — say, <HelloWorld message="world" />
— you have access to in the component’s this.props
. So, in the <HelloWorld>
component, this.props.message
is world
. Also, look a bit closer at the JSX part of the code:
return (
<h1>Hello {this.props.message}</h1>
);
You’ll notice first that you have to wrap the HTML in parentheses. Secondly, this.props.message
is wrapped in braces. The braces give you access to the component via this
.
Each component also has access to its “state.” With React, each component manages its state with a few simple API methods, getState
and setState
, as well as getInitialState
for when the component first loads. Whenever the state changes, the render
method simply re-renders the component. For example:
var Search = React.createClass({
getInitialState() {
return {
search: ""
};
},
render() {
return (
<div className="search-component">
<input type="text" onChange={this.changeSearch} />
<span>You are searching for: {this.state.search}</span>
</div>
);
},
changeSearch(event) {
var text = event.target.value;
this.setState({
search: text
});
}
});
React.render(<Search />, document.body);
In this example, the getInitialState
function simply returns an object literal containing the initial state of the component.
The render
function returns JSX for our elements — so, an input
and a span
, both wrapped in a div
. Keep in mind that only one element can ever be returned in JSX as a parent. In other words, you can’t return <div></div><div></div>
; you can only return one element with multiple children.
Notice the onChange={this.changeSearch}
. This tells the component to fire the changeSearch
function when the change event fires on the input.
The changeSearch
function receives the event
fired from the DOM event and can grab the current text of the input. Then, we call setState
and pass in the text. This causes render
to fire again, and the {this.state.search}
will reflect the new change.
Many other APIs in React are available to work with, but at a high level, what we did above is as easy as it gets for creating a simple React component.
Isomorphic JavaScript
With React, we can build “isomorphic” apps.
"i·so·mor·phic: “corresponding or similar in form and relations”"
This has already become a buzzword in 2015. Basically, it just means that we get to use the same code on the client and on the server.
This approach has many benefits.
Eliminate The FOUC
With AngularJS, Ember (for now) and SPA-type architecture, when a user first hits the page, all of the assets have to download. With SPA applications, this can take a second, and most users these days expect a loading time of less than two seconds. While content is loading, the page is unrendered. This is called the “flash of unstyled content” (FOUC). One benefit of an isomorphic approach to building applications is that you get the speed benefits of rendering on the server, and you can still render components after the page loads on the client.
The job of an isomorphic app is not to replace the traditional server API, but merely to help eliminate FOUC and to give users the better, faster experience that they are growing accustomed to.
Shared Code
One big benefit is being able to use the same code on the client and on the server. Simply create your components, and they will work in both places. In most systems, such as Rails, ASP.NET MVC, you will typically have erb
or cshtml
views for rendering on the server. You then have to have client-side templates, such as Handlebars or Hogan.js, which often duplicate logic. With React, the same components work in both places.
Progressive Enhancement
Server rendering allows you to send down the barebones HTML that a client needs to display a website. You can then enhance the experience or render more components in the client.
Delivering a nice experience to a user on a flip phone in Africa, as well as an enhanced experience to a user on a 15-inch MacBook Pro with Retina Display, hooked up to the new 4K monitor, is normally a rather tedious task.
React goes above and beyond just sharing components. When you render React components on the server and ship the HTML down to the client, React on the client side notices that the HTML already exists. It simply attaches event handlers to the existing elements, and you’re ready to go.
This means that you can ship down only the HTML needed to render the page; then, any additional things can be pulled in and rendered on the client as needed. You get the benefit of fast page loading by server rendering, and you can reuse the components.
Creating An Isomorphic Express App
Express is one of the most popular Node.js web servers. Getting up and running with rendering React with Express is very easy.
Adding React rendering to an Express app takes just a few steps. First, add node-jsx
and react
to your project with this:
npm install node-jsx --save
npm install react --save
Let’s create a basic app.jsx
file in the public/javascripts/components
directory, which requires our Search
component from earlier:
var React = require("react"),
Search = require("./search");
var App = React.createClass({
render() {
return (
<Search />
);
}
});
module.exports = App;
Here, we are requiring react
and our Search.jsx
component. In the App
render method, we can simply use the component with <Search />
.
Then, add the following to one of your routers where you’re planning on rendering with React:
require("node-jsx").install({
harmony: true,
extension: ".jsx"
});
All this does is allow us to actually use require
to grab .jsx
files. Otherwise, Node.js wouldn’t know how to parse them. The harmony
option allows for ECMAScript 6-style components.
Next, require in your component and pass it to React.createFactory
, which will return a function that you can call to invoke the component:
var React = require("react"),
App = React.createFactory(require("../public/javascripts/components/app")),
express = require("express"),
router = express.Router();
Then, in a route, simply call React.renderToString
and pass it your component:
router.get("/", function(req, res) {
var markup = React.renderToString(
App()
);
res.render("index", {
markup: markup
});
});
Finally, in your view, simply output the markup:
<body>
<div id="content">
{{{markup}}}
</div>
</body>
That’s it for the server code. Let’s look at what’s necessary on the client side.
Webpack
Webpack is a JavaScript bundler. It bundles all of your static assets, including JavaScript, images, CSS and more, into a single file. It also enables you to process the files through different types of loaders. You could write your JavaScript with CommonJS or AMD modules syntax.
For React .jsx
files, you’ll just need to configure your webpack.config
file a bit in order to compile all of your jsx
components.
Getting started with Webpack is easy:
npm install webpack -g # Install webpack globally
npm install jsx-loader --save # Install the jsx loader for webpack
Next, create a webpack.config.js
file.
var path = require("path");
module.exports = [{
context: path.join(__dirname, "public", "javascripts"),
entry: "app",
output: {
path: path.join(__dirname, "public", "javascripts"),
filename: "bundle.js"
},
module: {
loaders: [
{ test: /\.jsx$/, loader: "jsx-loader?harmony"}
]
},
resolve: {
// You can now require('file') instead of require('file.coffee')
extensions: ["", ".js", ".jsx"],
root: [path.join(__dirname, "public", "javascripts")],
modulesDirectories: ["node_modules"]
}
}];
Let’s break this down:
context
This is the root of your JavaScript files.entry
This is the main file that will load your other files using CommonJS’require
syntax by default.output
This tells Webpack to output the code in a bundle, with a path ofpublic/javascripts/bundle.js
.
The module
object is where you set up “loaders.” A loader simply enables you to test for a file extension and then pass that file through a loader. Many loaders exist for things like CSS, Sass, HTML, CoffeeScript and JSX. Here, we just have the one, jsx-loader?harmony
. You can append options as a “query string” to the loader’s name. Here, ?harmony
enables us to use ECMAScript 6 syntax in our modules. The test
tells Webpack to pass any file with .jsx
at the end to jsx-loader
.
In resolve
we see a few other options. First, extensions
tells Webpack to omit the extensions of certain file types when we require
files. This allows us just to do require(“./file”)
, rather than require(“./file.js”)
. We’re also going to set a root
, which is simply the root of where our files will be required from. Finally, we’ll allow Webpack to pull modules from the node_modules
directory with the modulesDirectories
option. This enables us to install something like Handlebars with npm install handlebars
and simply require(“handlebars”)
, as you would in a Node.js app.
Client-Side Code
In public/javascripts/app.js
, we’ll require in the same App
component that we required in Express:
var React = require("react"),
App = React.createFactory(require("components/app"));
if (typeof window !== "undefined") {
window.onload = function() {
React.render(App(), document.getElementById("content"));
};
}
We’re going to check that we’re in the browser with the typeof window !== “undefined”
. Then, we’ll attach to the onload
event of the window, and we’ll call React.render
and pass in our App()
. The second argument we need here is a DOM element to mount to. This needs to be the same element in which we rendered the React markup on the server — in this case, the #content
element.
The Search
component in the example above was rendered on the server and shipped down to the client. The client-side React sees the rendered markup and attaches only the event handlers! This means we’ll get to see an initial page while the JavaScript loads.
All of the code above is available on GitHub.
Conclusion
Web architecture definitely goes through cycles. We started out rendering everything on the server and shipping it down to the client. Then, JavaScript came along, and we started using it for simple page interactions. At some point, JavaScript grew up and we realized it could be used to build large applications that render all on the client and that use the server to retrieve data through an API.
In 2015, we’re starting to realize that we have these powerful servers, with tons of memory and CPU, and that they do a darn good job of rendering stuff for us. This isomorphic approach to building applications might just give us the best of both worlds: using JavaScript in both places, and delivering to the user a good experience by sending down something they can see quickly and then building on that with client-side JavaScript.
React is one of the first of what are sure to be many frameworks that enable this type of behavior. Ember’s developers are already working on isomorphic-style applications as well. Seeing how this all works out is definitely going to be fun!
Resources
- React
- React Lessons, Egghead.io
- Express
- “Isomorphic Examples,” React.rocks
- Webpack, GitHub
- JSX Loader for Webpack, Pete Hunt, GitHub
Further Reading
- How To Scale React Applications
- The Beauty Of React Native
- Server-Side Rendering With React, Node And Express
- Styled-Components: Enforcing Best Practices In Component-Based Systems