BHW Group Blog

Website Design with Clojure and Reagent

Website Design and Development in Reagent

Welcome to our third article discussing website design and development in Clojure. Clojure is an exciting Lisp language that offers full-stack web development, compiling to the JVM for server execution and to JavaScript for client side execution. In our first article we presented Clojure as a server-side programming language by demonstrating a simple REST API for a Single Page Application (SPA). In our second article we demonstrated Om, a popular client-side scripting framework using ClojureScript and React. Today we look at Reagent, a second philosophy to client-side web development in Clojure.

Clojure logo

Why Reagent?

Reagent is a ClojureScript library that provides a programming and templating interface for Facebook's React library. Why does the Clojure community need a second client-side framework targeting React? To answer that question, you have to consider React's history. React's early releases required the use of a proprietary API that generated HTML and did not use traditional markup language. In React's API, HTML DOM elements are constructed from similarly named React API functions. React is a novel way to synchronize client-side state and the DOM, but with an already steep learning curve, Facebook decided that the API added too much additional complexity for newcomers. In order to spur adoption, and partly remove the need to learn React's proprietary API, Facebook created a second method to integrate with React using familiar HTML templates via a JavaScript langauage extension, JSX. Today Facebook recommends React developers use JSX instead of the React API.

Om was released before Reagent and targets React's API instead of JSX. Developers must learn the React API to program Om. Reagent was released after Om and targets JSX instead of the React API. This means that programming in Reagent is immediately familiar to developers who know HTML. Learning the React API is not necessary in Reagent.

React logo

Reagent and Om Compared

To demonstrate the differences between Om and Reagent we will build a small example component. We'll create a simple component that should display the current time in the following HTML structure.

<div class='time-component'>
  <h3 class='time-title'>The current time is:</h3>
  <p class='time'>{{24 hour time format}}</p>
</div>

Similarities

Both frameworks start by rendering their component into an existing DOM element specified by an ID. A typical HTML setup for both frameworks would look like the following:

<div id='simple-component'></div>

Om wires up our "simple-component" and renders content into this element as follows:

(om/root simple-component app-state
    {:target (. js/document (getElementById "simple-component"))}))

Reagent's wire up is very similar, but slightly more compact:

(reagent/render-component [simple-component] (.getElementById js/document "simple-component")))

Where they differ

From here the two frameworks diverge, first we'll show Om's approach to implementing our "simple-component".

(def app-state
  (atom
   {:current-time (js/Date.)}))

(defn simple-component [app owner]
  (reify
    om/IWillMount
    (will-mount [this]
                (js/setInterval #(om/update! app [:current-time] (js/Date.)) 1000))
    om/IRender
    (render [this]
            (let [time-str (-> (:current-time app) .toTimeString (clojure.string/split " ") first)]
              (dom/div #js {:className "time-component"}
                       (dom/h3 #js {:className "time-title"} "The current time is:")
                       (dom/p #js {:className "time"} time-str))))))

Here our Om code uses the React API Lifecycle to setup a JavaScript interval that updates our app state every second. Om uses a single shared app state between all components which in our example is called "app-state" and contains the key "current-time". Om manages app-state through clojure atoms which provide managed access to synchronous state. Now the equivalent Reagent implementation:

(defn simple-component []
  (let [current-time (atom (js/Date.))]
    (fn []
      (js/setTimeout #(reset! current-time (js/Date.)) 1000)
      (let [time-str (-> @current-time .toTimeString (clojure.string/split " ") first)]
        [:h3.time-title "The current time is:"]
        [:p.time time-str]))))

Our Reagent source code closely resembles a typical HTML document, which is immediately more comfortable for web developers with limited React API experience. In our opinion the Reagent code is more concise and compact while being easier to read. Some of the syntax complexity in Om comes from its dependence on the clojure reify macro to create a component which implements the React Lifecycle API. Reagent hides this detail from the developer by handling the API interface for the user. In our example, Reagent is able to use the setTimeout event instead of setInterval because our Reagent component returns a function for rendering instead of the actual rendered result. Each time the component is rendered in Reagent we set a new timer. Our Om code only wires up once in the IWillMount API interface so we use setInterval to ensure we continue to fire updates every second.

In addition to the syntax and API differences, Reagent and Om take a very different approach to state management. Instead of following the global app state approach like Om, Reagent uses its own implementation of the atom class which re-renders dependent components based on an atom being dereferenced. This approach allows Reagent to use local component atoms for state information that isn't shared with other components. Our example makes use of this local state feature by declaring our current-time as an atom within the Reagent component itself.

Now that we're covered some of the differences, let's go step by step in creating a new Reagent project using Leinengen.

Starting a Reagent Project

In this article we'll continue to use the 2014 World Cup datasource shown in the prior two articles. To start our Reagent example we first use a Leinengen template, reagent-template.

lein new reagent <name> +test

Second, we make a few changes to the project files to include our dependencies, including clutch for accessing CouchDB.

CouchDB logo
[ring/ring-json "0.3.1"]
[com.ashafa/clutch "0.4.0"]

Third step, we need to change our ring middleware and include our module. Check the GitHub repo for the complete source. The most interesting changes happen around the construction of our ring middleware. The sample below shows how to use the closure threading macro -> to wrap our middleware and our JSON handlers around the request.

(def app
  (-> (handler/api app-routes)
      (middleware/wrap-json-body)
      (middleware/wrap-json-response)))

Finally we start our development server and figwheel, which will start a REPL with ClojureScript and automatically compile and update our client code when we make changes to the files.

lein figwheel

and we get...

java.lang.NoClassDefFoundError: org/apache/http/pool/ConnPoolControl, compiling:(com/ashafa/clutch/http_client.clj:1:1)
  at clojure.lang.Compiler.load(Compiler.java:7142)
  at clojure.lang.RT.loadResourceScript(RT.java:370)
  at clojure.lang.RT.loadResourceScript(RT.java:361)
...
Caused by: java.lang.ClassNotFoundException: org.apache.http.pool.ConnPoolControl
  at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
...

This error indicates that we have dependency conflicts. Leinengen gives us a handy tool to diagnose these issues, deps :tree. Deps will print out a project's dependencies in a tree form, and most importantly, will show conflicts at the top of the output.

lein deps :tree
Possibly confusing dependencies found:
[com.ashafa/clutch "0.4.0"] -> [clj-http "0.5.5"]
 overrides
[leiningen "2.5.1"] -> [clj-http "0.9.2" :exclusions [crouton]]
...
[com.ashafa/clutch "0.4.0"] -> [clj-http "0.5.5"] -> [org.apache.httpcomponents/httpclient "4.2.1"]
 overrides
[leiningen "2.5.1"] -> [clj-http "0.9.2" :exclusions [crouton]] -> [org.apache.httpcomponents/httpmime "4.3.3" :exclusions [org.clojure/clojure]] -> [org.apache.httpcomponents/httpclient "4.3.3"]
 and
[leiningen "2.5.1"] -> [leiningen-core "2.5.1"] -> [org.apache.maven.wagon/wagon-http "2.7"] -> [org.apache.httpcomponents/httpclient "4.3.5"]
 and
[leiningen "2.5.1"] -> [clj-http "0.9.2" :exclusions [crouton]] -> [org.apache.httpcomponents/httpclient "4.3.3" :exclusions [org.clojure/clojure]]

Correcting Dependencies

Resolving conflicts requires study, and in our case we can force clojure to use the latest libraries for httpclient and clj-http. These are API compatible with the version clutch links. To correct these dependency conflicts we need to help clojure's compiler figure out what to do. First, we'll move clutch to the bottom of our dependency list and then we'll add these two dependency overrides.

[com.ashafa/clutch "0.4.0"]
[org.apache.httpcomponents/httpclient "4.3.5"]
[clj-http "0.9.2" :exclusions [crouton]]
[cljs-ajax "0.3.10"]]

Now we're ready to start our server again with lein figwheel

Figwheel: Starting server at http://localhost:3449
Focusing on build ids: app
Compiling "resources/public/js/app.js" from ["src/cljs" "env/dev/cljs"]...
Successfully compiled "resources/public/js/app.js" in 12.159 seconds.
Started Figwheel autobuilder see: figwheel_server.log

Launching ClojureScript REPL for build: app
Figwheel Controls:
          (stop-autobuild)           ;; stops Figwheel autobuilder
          (start-autobuild [id ...]) ;; starts autobuilder focused on optional ids
          (switch-to-build id ...)   ;; switches autobuilder to different build
          (reset-autobuild)          ;; stops, cleans, and starts autobuilder
          (build-once [id ...])      ;; builds source once time
          (clean-build [id ..])      ;; deletes compiled cljs target files
          (add-dep [org.om/om "0.8.1"]) ;; add a dependency. very experimental
  Switch REPL build focus:
          :cljs/quit                 ;; allows you to switch REPL to another build
    Docs: (doc function-name-here)
    Exit: Control+C or :cljs/quit
 Results: Stored in vars *1, *2, *3
Prompt will show when figwheel connects to your application
To quit, type: :cljs/quit

Conclusion

React is an exciting client-side framework that we use on many new projects. Reagent makes React more accessible to Clojure developers who don't know React's API. We feel that our Reagent code is easier to read than our Om code, especially for deeper DOM trees. Reagent's advantages over Om stem from adopting the same strategy Facebook has taken with React's JSX interface. Be sure to check out the entire project's source on GitHub. Our next article will be out in a few weeks and will compare the performance between SPA's built with Angular, React, JavaScript, and Clojure.

Do you need an expert in web development? With a team of web development specialists covering a wide range of skill sets and backgrounds, The BHW Group is prepared to help your company make the transformations needed to remain competitive in today’s high-tech marketplace.

You may also like

Categories:
Brett is a customer-focused executive who works closely with his team members and often takes a hands-on role in project execution. He blends strong business skills with deep technology expertise, allowing him to bridge the often insurmountable gap between business and IT. By mastering both domains, Brett can quickly conceive and operationalize new solutions that are technically elegant and commercially successful.