Clojurescript Frontend Development For Novices
IntroductionLink to Introduction
I'm an old backend developer. I have occasionally done user interfaces using the tools that dominated that era in the Java world (e.g. JSP, JSF, etc.). At Metosin, I have had a chance to start learning modern single-page application development. In this blog post, I list the tools I have used when implementing modern single-page applications. I also describe some of my experiences during the frontend development learning process. I hope that other Clojurescript frontend novices like me can benefit from this blog post.
RationaleLink to Rationale
Why does an old backend developer want desperately to learn to implement modern single-page applications? The reason is that nowadays, a single developer can create real full-stack turn-key solutions:
- The cloud infrastructure for running the applications (i.e. a virtual data center) using infrastructure as code.
- The backend applications running in that cloud infrastructure.
- The frontend applications (web apps) that the end-users use to access the functionalities provided by the backend applications and infrastructure.
If you master all these areas, you can implement quite complex systems single-handedly. It is truly a powerful feeling. In my current project, I'm able to do just that: I have implemented a cloud infrastructure for ingesting, processing, and storing IoT events, a backend that provides an API for the events, and now the web app that can be used to visualize the IoT events, provide various notifications, alarms, etc. You can read more about the IoT platform implementation in my blog posts AWS IoT First Reflections and AWS IoT Storage Considerations.
If you are interested in the re-definition of the concept of a full-stack developer, read my blog post about it: Real Full-Stack Developer.
What is a Single-page Application?Link to What is a Single-page Application?
Wikipedia says:
A single-page application (SPA) is a web application that interacts with the user by dynamically rewriting the current web page with new data from the web server instead of the default method of a web browser loading entire new pages.
This paradigm was a bit hard to grasp for an old backend developer who, in his early days, used to create HTML pages by server-side rendering technologies. But in layman's terms, a single-page application gives the capability to divide the HTML app in distinct parts which can be re-rendered individually. This is a powerful paradigm, exactly how SPA frameworks like React manipulate the HTML dom tree.
ReactLink to React
React is one of the most popular modern SPA frameworks. As React home page says:
React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.
You can see quite clearly how React is a natural fit for implementing single-page applications: you define your frontend with state and views - when the state of a particular view changes, React re-renders that part of the single-page application.
Clojure and ClojurescriptLink to Clojure and Clojurescript
The Clojure programming language is a modern Lisp dialect that is hosted on JVM. ClojureScript is Clojure for browsers (the Clojurescript compiler emits Javascript - the language running in browsers). It is really wonderful to be able to use the same powerful, data-oriented, immutable language on both sides. You can also share code between the backend and frontend in Clojure full-stack apps. And you can even start a REPL in your browser and connect your Clojurescript to it, just like on the backend side.
HiccupLink to Hiccup
Hiccup is a natural way of representing HTML in Clojure. You can use the Hiccup library in the frontend. This way, your HTML code uses Clojure data structures (vectors and maps) for expressing the HTML entities and their parameters. I like the Hiccup syntax; it makes HTML code very concise and readable. Example:
(defn dashboard-cards []
(e-util/clog "dashboard-cards")
(let [things @(re-frame/subscribe [::selected-things])]
(when (not-empty things)
[:div
[:div.mb-4
[:p
[:strong "Utilizations"]]]
[:div.columns.is-multiline.is-mobile.mb-4
(for [thing things]
^{:key thing}
[card thing])]])))
You just put the HTML elements (like div
) as Clojure keywords as the first element of a Clojure vector. You can add CSS classes using the .
(dot) notation: .mb-4
(this class is a Bulma size helper).
ReagentLink to Reagent
Reagent is a simple ClojureScript interface to React. You can learn Reagent in a short time by reading the excellent Reagent documentation and experimenting yourself.
The most important parts of Reagent for me were to learn these things:
- You can create two kinds of Reagent components (well, three kinds, but a novice like me just needs the first two). These two functions are pretty well documented in Reagent documentation Creating Reagent Components. The first function just returns HTML code. The second function returns a function. Read the documentation and understand the difference.
- The Reagent component is part of the React app and gets re-rendered if the Reagent atoms change. But, you have to put those Reagent atoms inside the Reagent function to notify the function that it needs to re-render when the state changes. You can use the Reagent atoms (ratoms), or the re-frame subscriptions (another important thing to realize is that the re-frame subscriptions are ratoms).
Re-frameLink to Re-frame
Re-frame is a ClojureScript framework that provides nice abstractions for a React-based application. You don't have to use re-frame - you can implement React-based web apps just using Clojurescript and Reagent. But the benefit of using re-frame is that it gives a nice mechanism for the "state and view" model, which is the backbone of the React framework. I found this quote in re-frame documentation:
Each time application state changes, a-query-fn will be called again to compute a new materialised view (a new computation over app state) and that new value will be given to all view functions which are subscribed to :some-query-id. These view functions will then be called to compute the new DOM state (because the views depend on query results which have changed).
I first read the re-frame documentation about a year ago and had some challenges creating a mental model about it. This autumn, I implemented a single-page application based on the stack described in this blog post and read the re-frame documentation again. This time I felt like all the pieces suddenly fitted together. Re-frame provides a so-called application database, app-db, in which you keep the application state. Re-frame then provides a very consistent way to change this application state and subscribe to parts of that state. In React components implemented using Reagent, you can then subscribe to those parts that are important for that specific view (the Reagent function, remember?). The React component automatically gets notified through the subscription model and re-renders the component view when the state changes. This is really a very powerful paradigm, and once you grasp it, it makes implementing modern interactive web apps quite easy for an ordinary frontend novice like me.
Shadow-cljsLink to Shadow-cljs
Shadow-cljs provides the glue between the Clojurescript and Javascript worlds. It is a build tool that watches changes in your Clojurescript code and live-reloads the changes in your web app in the browser.
BulmaLink to Bulma
Bulma is a straightforward CSS framework that even a complete frontend novice learns fast and can create nice-looking responsive web apps in a short time (instead of spending hours trying to understand complex CSS hierarchies). If you prefer the easy way like me, just use some simple CSS framework with the default configurations the framework provides out of the box. Do not jump into the manual CSS tweaking rabbit hole.
A word of warning. In Metosin Slack, I got some comments that learning CSS is crucial for a frontend developer. So, please take my advice in meaning that a novice frontend developer can start using some CSS framework as Bulma. Still, you should also learn CSS things like the CSS syntax, specificity rules, box model, flexbox / float based layout, etc.
Javascript InteropLink to Javascript Interop
Like Clojure has excellent interop with Java, Clojurescript also has excellent interop with Javascript. You don't need the interop that often; I actually had some challenges to find an example in my Clojurescript code base, but finally found one:
now-ts (.getTime (js/Date.))
I.e., creating a Date
object and then calling its getTime
method.
Using React ComponentsLink to Using React Components
You can use any React component implemented using Javascript (or e.g. Typescript, I guess), quite easily in your clojurescript app:
(ns xxxx.frontend.view.components
(:require [reagent.core :as r]
["react-select" :default Select]))
(defn select [{:keys [on-change options is-multi is-searchable value]}]
(r/create-element
Select
#js {:components components
:onChange on-change
:isMulti is-multi
:value (clj->js value)
:isSearchable is-searchable
:options (clj->js options)}))
That's it. I have created a generic select component that uses the React Select component. As you can see it is really simple to wrap native React components in Clojurescript. Now I can use the component in my app:
[e-components/select
{:is-multi true
:value (->> selected-dimensions
sort
(map (fn [item] {:value item, :label item})))
:options dimension-options
:on-change (fn [selected-items]
(re-frame/dispatch [::set-selected-dimensions
(map (fn [item]
(get item "value"))
(js->clj selected-items))]))}]]
Putting It Altogether - an ExampleLink to Putting It Altogether - an Example
Let's provide an example of using the bits and pieces introduced earlier (Hiccup, Reagent, re-frame, Bulma...) In my IoT visualizing web app, I have a dashboard page to select various IoT devices, dimensions, time series, etc. The app shows multiple graphs regarding the metrics. The dashboard page looks like this:
(defn dashboard-page
"dashboard view."
[match]
(let [customer-id @(re-frame/subscribe [::e-state/customer-id])]
(re-frame/dispatch-sync [::initialize-dashboard-db])
(re-frame/dispatch-sync [::set-loading-text "Initial loading data..."])
(load-data customer-id)
(fn []
(let [events (-> @(re-frame/subscribe [::e-event/events-data]) :data :events)
alarms (-> @(re-frame/subscribe [::e-alarm/alarms-data]) :data :alarms)
; ...
ready (and events alarms utilizations min-max-times)]
[:div.container
(if-not ready
[loading]
[:div
[dashboard-header]
[dashboard-cards]
[dashboard-graphs]])
I.e., we first subscribe certain data from the re-frame application database (e.g., events and alarms). We are ready if we have loaded all data and done all the calculations. If we are not yet ready, we show the loading information. If we are ready, we show the dashboard header, dashboard cards, and dashboard graphs. The dashboard-page
is a type-2 Reagent function. dashboard-page
calls other Reagent functions (e.g. loading
and dashboard-header
), which in turn provide their part of the Hiccup that finally renders as HTML.
Some Productivity Tools and TricksLink to Some Productivity Tools and Tricks
This chapter provides some tools and tricks that made my life easier when implementing single-page applications. I learned most of these tricks from my Metosin colleagues, which helped me understand modern web application development in our screen sharing sessions.
Chrome DevToolsLink to Chrome DevTools
Chrome DevTools is a handy tool. You definitely want to learn to use it. As a frontend novice, the DevTools console was a life-saver when figuring out what was happening, e.g., in various event functions. Another important tool is the CSS view. In the CSS view, you can click any part of your web app, and you get to see the particular CSS configuration of that entity. There is also a button which you can see the web app in either desktop or mobile view - a great way to test the responsiveness of your web app quickly.
React Developer ToolsLink to React Developer Tools
React Developer Tools is also a must. There is a Components view that lists all React components that are part of your web app. Another useful feature is the "Highlight updates when components render" - I used this a couple of times when I had some issues with e.g., infinite loops (some re-rendering caused state changes which caused re-rendering, which caused state changes, etc.).
More ToolsLink to More Tools
You might find these tools also helpful.
- Reagent-dev-tools. This is a Metosin tool, and we use it quite a lot at Metosin. You can use it as simple as
(dev-tools/start! {:state-atom re-frame.db/app-db})
to provide a tree view to the re-frame db. You can also create a sophisticated dev panel for setting up various fixtures. E.g., a fixture that can reset the app in a certain state (e.g., "the user" runs through a complex business scenario). Or to choose a certain role to test the security features of the application. - re-frame-10x. This is a tool from the re-frame developers. I'm pretty sure it is useful; I just haven't used it myself (the Reagent-dev-tools has provided most of the same functionality I have needed.)
- cljs-devtools. This tool adds various enhancements into Chrome DevTools for ClojureScript developers (e.g., better presentation of ClojureScript values, more informative exceptions, etc.)
- Hashp. This is absolutely a fabulous small tool. I quite often add the
#p
macro to some piece of code to refresh my memory what it returns, e.g.selected-things #p @(re-frame/subscribe [::selected-things])
.
CSS TricksLink to CSS Tricks
For a novice frontend developer, I recommend using some CSS framework like Bulma and using the components with the configuration provided with the framework. At least I found the CSS tweaking to be a tar pit best to avoid. But there were moments when I had to check if some issue was caused by CSS or the Javascript library. E.g., I had some issues to making the Vega-Lite generated graphics responsive. My colleague, a CSS guru, quickly added CSS style with a colorful border and added this CSS class to the container of the graphics entity to provide a visual aid to see the boundaries the graphics comprised in the HTML page.
Learn to Understand the Asynchronous ModelLink to Learn to Understand the Asynchronous Model
The browser works using an asynchronous model. This is important to understand when implementing a single-page application. It took some time for an old backend developer to create an asynchronous mental model in which you visualize in your head how things start asynchronously and where you have to subscribe to check if some data has returned and what happens next. But it's not rocket science. You learn it by doing, and your mental model grows stronger every day.
Find a CommunityLink to Find a Community
Metosin is just a fabulous community to learn new things. There are always specialists who are really good at various aspects of software development, and they are willing to help; read more in my On Superorganisms blog post. E.g., Juho provided excellent comments for this blog post. (Can you believe this: the Reagent main developer reviews your frontend related blog post.)
But if you don't have a community as I have, not all hope is lost. Reach out to other Clojure developers, e.g., various SIGs where you live. An excellent place to ask for help is the Clojurians Slack - there are channels for Clojurescript, Reagent, and re-frame.
ConclusionsLink to Conclusions
Clojure is a wonderful language. And using Clojurescript with Reagent and other tooling, a novice frontend developer can quite quickly start implementing good-looking interactive web apps.
The DogLink to The Dog
The blog picture is my new office dog, Murre. Murre is a Miniature Schnauzer, and he likes to relax and chew his master's toes under my table while his master is writing blogs.