solovyov.net

ClojureScript: how to begin

5 min read · cljs

This is a short guide which intends to give you shortest path to writing ClojureScript with React and reloadless workflow. It doesn’t teach you how to write actual applications, only how to set up all the necessary (and boring) boilerplate. For an application example see snake or flappy-bird. Let’s get started.

Steps

Get Leiningen

First you need to have Leiningen installed. I think that the default method - having lein in your $PATH - works best, but if you’re willing to struggle with your package manager, good luck. If you are a Windows user, there is an installer (check the link above).

Create project

After that, find a place where your project will reside, start a command line shell there, and run:

lein new figwheel yourapp

This will create a yourapp directory with a project named yourapp inside (so choose your name wisely), using a fighweel Leiningen template. This will give you basic initial structure for a ClojureScript application (regular lein new yourapp will give you a Clojure project and you’ll have to change a lot of stuff there).

Configure project

Your project dependencies and configuration are in project.clj: go there and change url, description and check out the rest of it. We’re going with a different wrapper for React, so replace [om "0.7.1"] with [quiescent "0.1.4"]. Read below for a reason.

index.html

Open resources/public/index.html in your editor and uncomment script tag with reference to react.

Imports

Now we’re going to change core.cljs a bit. Delete all the code from this file (well, your ns declaration should stay, but we’re going to change all requirements). Set up some requires inside of ns call:

(ns yourapp.core
  (:require [figwheel.client :as fw]
            [sablono.core :as html :refer-macros [html]]
            [quiescent :as q :include-macros true]))

I’m intentionally not giving all the code at once here so that you’ll be forced to understand what’s going on. At least I hope so. Anyway, if something’s bugging you, send me an email.

The code

Set up our data store:

(defonce world (atom {:text "Hello!"}))

We are using defonce instead of def here to make our world only defined once, on initial load of an application. This means that changed state of your application will persist between source file reloads - and you can reset your state by reloading a page.

…and main component:

(q/defcomponent Root
  [data]
  (html
    [:h1 (:text data)]))

I’m using sablono’s html macro here, which renders Hiccup-like data structures to React virtual DOM components, since it makes it easier for me to distinguish between HTML markup and my code. You could write it as (d/h1 nil (:text data)) (do not forget to put [quiescent.dom :as d] in your :require section.

…and renderer:

(defn render [data]
  (q/render (Root data)
    (.getElementById js/document "main-area")))

Now watch for data changes:

(add-watch world ::render
    (fn [_ _ _ data] (render data)))

Finally, react to files being reloaded:

(fw/watch-and-reload :jsload-callback
  (fn [] (swap! world update-in [:tmp-dev] not)))

This code changes a non-relevant variable when some file is changed (and saved), forcing your application to re-render.

And kick-off rendering now (again, we’re using defonce here so that this expression executes only once - in general render is called by the world watcher):

(defonce *whatever* (render @world))

Run

Run this in your project directory and open http://localhost:3449:

lein figwheel dev

This starts a watcher, which recompiles ClojureScript files as you change them and sends them to your browser.

That’s it

Now keep your app tab open in the browser, and go change [:h1 (:text data)] to [:h3 (:text data)] (or something else, it’s up to you). Your browser now should immediately change rendered HTML. That’s how you start. :)

Why Quiescent

Om is better known and (it seems) more widely used, so why Quiescent? Because when you’re working with Om, you need to know not only how React works, but also Om semantics, which is much more complex and confusing, especially when you’re just starting. It’s API is bigger than React’s, and while there are advantages and reasons to use it, going with Quiescent means your learning curve will be much lower.

Plus Quiescent forces you to think about your data layout in a single big, while Om provides easy access to component local state - and I find that most developers (me not excluded) are eager to use it, even if it’s not necessary (and it’s not most of the time). Anyway, go and read Quiescent’s rationale, it’s a good write-up.

If you like what you read — subscribe to my Twitter, I always post links to new posts there. Or, in case you're an old school person longing for an ancient technology, put a link to my RSS feed in your feed reader (it's actually Atom feed, but who cares).

Other recent posts

Server-Sent Events, but with POST
ngrok for the wicked, or expose your ports comfortably
PostgreSQL collation
History Snapshotting in TwinSpark
Code streaming: hundred ounces of nuances