React is a JavaScript library similar to AngularJS, Ember.js, and many others. Initially developed by Facebook in 2013, React is now an open source project. What sets React apart from other frameworks is that it focuses simply on rendering in the most performant way possible. React’s performance has been written about in many blog articles and is considered a top performer for rendering large and complicated views.
React’s performance comes from its “virtual DOM” approach to rendering. Any framework can provide acceptable performance with small data sets, but as the amount of data grows React clearly separates itself from the competition. React achieves high performance with large data sets by simply doing less work. As application models change, React only re-renders the differences as calculated by comparing it’s virtual DOM to the browser’s HTML DOM. Rendering only the differences makes React incredibly fast and efficient, allowing it to scale for handling large complex views that would perform poorly if rendered in other client-side frameworks. React is still the “new kid on the block” and at the time of this writing has around 3,000 questions on stackoverflow versus over 100,000 for a mature framework like AngularJS.
Why React.js?
React on its own isn’t a drop-in replacement for building a Single Page Application (SPA), especially when compared to the features in a mature framework like AngularJS. React simply acts as a view engine and does not implement the other necessary requirements of a SPA such as routing and model management.
To build a working SPA, React must be combined with other libraries or integrated with existing SPA frameworks. It’s possible to integrate React with AngularJS such that React renders the view and relies on AngularJS to handle the other SPA responsibilities.
The ease of working with AngularJS and the other JavaScript MVC frameworks has caused an explosion in the number of SPA applications being written today. Many developers embrace this JavaScript-based ecosystem.
JavaScript as a language is maturing with new language features such as generators and immutable variables that help create cleaner and more reliable code. While developers eagerly await these JavaScript improvements in ECMA Script 6 and 7, other developers have started using new languages for client development.
A language we’ll be discussing in this article is Clojure, a Lisp-based language that compiles and executes on the JVM and cross-compiles to JavaScript with ClojureScript. Cross-compiling to JavaScript is possible in a large variety of languages.
Using ClojureScript a developer can rely on Clojure’s compiled features like static type safety and immutability while cross-compiling into JavaScript that can be run in any browser. Many cross-compilers even support source maps so that debugging in the browser references the original Clojure source files and line numbers rather than the cross-compiler generated JavaScript.
This exciting new option has become a popular alternative for client-side development and now projects like Om are going a step fruther to provide a Clojure interface to React. With Om a developer can write code in Clojure and target React for an easy path to building a SPA.
How To Build A Single Page Application (SPA)
Let’s get started using React and Om to build a SPA.
Prerequisites
Unlike most JavaScript projects we’ll actually start by installing a compiler and code generator for Clojure, Leiningen. After that you will also need a JDK, and a Clojure-friendly editor. Light Table is a popular editor and we also recommend Sublime with some plug-ins as a second option. To make getting started easy, the open source community has built a minimal Om template for Leiningen that we will use, mies-om. Open a command prompt and type the following:
lein new mies-om spa-tutorial
Using mies-om, the above command instructs Leiningen to create our skeleton project. As we mentioned earlier, React alone isn’t enough to build a SPA so now we’ll add third-party modules to accomplush routing. Below we will setup
Secretary for client-side routing. You’ll also notice that we include om-tools and http-kit which include helpers and macros to make Om programming easier. To include these add-ons edit your project.clj file to look like this:
clojure
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-2755"]
[org.clojure/core.async “0.1.346.0-17112a-alpha"]
[org.omcljs/om "0.8.8"]
[prismatic/om-tools "0.3.11"]
[http-kit "2.1.19"]
[secretary “1.2.3”]]
also edit your core.cljs file to look like this:,/p>
clojure
(ns spa-tutorial.core
(:require [om.core :as om :include-macros true]
[om-tools.dom :as dom :include-macros true]
[om-tools.core :refer-macros [defcomponent]]
[secretary.core :as sec :include-macros true]
[goog.events :as events]
[goog.history.EventType :as EventType])
At this point you should test your changes. From the command prompt in your “spa-tutorial” directory, execute the following:
lein cljsbuild once spa-tutorial
This command instructs Leiningen to pull the new dependencies and build our source. Leiningen will also cross-compilie our Clojure to JavaScript which you can view in the “/out” folder. In this folder you will see the JavaScript output from the core.cljs file as well as our dependent libraries like Om, React, and Secretary. If your code is working you will see a “Hello world!” message when opening a browser to the index.html file.
In order to understand how this works open the core.cljs file where you’ll see our simple React component:
clojure
(fn [app owner]
(reify om/IRender
(render [_]
(dom/h1 nil (:text app)))))
This simple React component implements the IRender interface defined in the React lifecycle. Our component produces an H1 containing the text from our app-state atom. Currently the app-state atom contains “Hello world!”. Try changing this a few times and run `lein cljsbuild once spa-tutorial` to see it change in the browser.
Views
Our simple “Hello world!” is a long way from a real SPA. Let’s add a few more views and routes to build a simple SPA. In the core.cljs file, make the following edits:
clojure
(ns spa-tutorial.core
(:require [om.core :as om :include-macros true]
[om-tools.dom :as dom :include-macros true]
[om-tools.core :refer-macros [defcomponent]]
[secretary.core :as sec :include-macros true]
[goog.events :as events]
[goog.history.EventType :as EventType])
(:import goog.History))
(enable-console-print!)
(sec/set-config! :prefix "#")
;;setup history API
(let [history (History.)
navigation EventType/NAVIGATE]
(goog.events/listen history
navigation
#(-> % .-token sec/dispatch!))
(doto history (.setEnabled true)))
;;components
;;navigation-view will be shared by our three main components
(defn navigation-view [app owner]
(reify
om/IRender
(render
[_]
(let [style {:style {:margin "10px;"}}]
(dom/div style
(dom/a (assoc style :href "#/")
"Home")
(dom/a (assoc style :href "#/contact")
"Contact")
(dom/a (assoc style :href "#/about")
"About"))))))
;;home page component
(defn index-page-view [app owner]
(reify
om/IRender
(render
[_]
(dom/div
(om/build navigation-view {})
(dom/h1 "Index Page")))))
;;contact page component
(defn contact-page-view [app owner]
(reify
om/IRender
(render
[_]
(dom/div
(om/build navigation-view {})
(dom/h1 "Contact Page")))))
;;about page component
(defn about-page-view [app owner]
(reify
om/IRender
(render
[_]
(dom/div
(om/build navigation-view {})
(dom/h1 "About Page")))))
;;setup secretary routes
(sec/defroute index-page "/" []
(om/root index-page-view
{}
{:target (. js/document (getElementById "app"))}))
(sec/defroute contact-page "/contact" []
(om/root contact-page-view
{}
{:target (. js/document (getElementById "app"))}))
(sec/defroute about-page "/about" []
(om/root about-page-view
{}
{:target (. js/document (getElementById "app"))}))
;;initialization
(defn main []
(-> js/document
.-location
(set! "#/")))
(sec/dispatch! “/")
With this change we have removed the simple template component and added four new components: index-page, contact-page, about-page, and the shared component, navigation-view. These components are registered with Secretary via specific routes using the `sec/defroute` statement. When the client-side route changes in the browser, Secretary overwrites the contents of React’s virtual DOM <div id=”app”> element. Becausue React compares the virtual DOM to the browser it quickly sees this change and renders the changed contents.
In our code each React component is a Clojure function built with Om so that it implements the IRender interface. Om makes it easy to build HTML using methods such as `dom/h1` which renders an HTML H1 element. React is based on component composition and Clojure functions make this easy by simply calling each other as shown in the interaction with the navigation-view to create our component hierarchy. Clojure functions are a nice approach to breaking up functionality into smaller, reusable components.
Conclusion
Clojure is an easy language to learn with its small number of forms. If you’ve worked with a functional language before it should be easy to pick up quickly. Once you begin writing SPAs with Clojure and Om you’ll want to investigate the many tools and utilities provided by ClojureScript and Leiningen. You can evaluate Clojure forms via a Clojure REPL connected to your browser window. Forms fed to the REPL console are evaluated and the side-effects are rendered into your browser in real-time. A second productivity enhancement is enabling reloading of your cross-compiled JavaScript with a project like Figwheel. These tools make developing Single Page Applications in Clojure more productive and can help when debugging your code.
If you enjoyed this article, please consider visiting our blog. We post frequent examples, guides, and comparisons of technologies, frameworks, and languages.
The BHW Group is an Austin-based web & mobile application development company. We have released over 300 applications using a wide array of frameworks and languages. We love tinkering with, testing, and writing about emerging and established technologies.