Guide to Reagent

Reagent is a ClojureScript wrapper around React. It gives you a way to easily create React components. Reagent has three main features that make it easy to use: using functions to create React components, using Hiccup to generate HTML, and storing state in Reagent Atoms. Reagent lets you write React code that is concise and readable.

React components are functions

Form-1 component

When we're using React from ClojureScript, we are typically using React in a functional programming way. That is, we want to define a function from inputs to DOM tree. Reagent makes this as easy as possible. Just define a function that returns Hiccup. This is called a Form-1 component. The function corresponds to the React render() method.

(defn green-button [txt]
  [:button.green txt])

Form-2 component

The next case is slightly more complicated. If you need to initialize something when the component is created, you use a Form-2 component. Form-2 components are functions that return their render function. The outer function will be called once to get the render function. Then the render function will be called each time the component needs to render. If you wrap that inner render function in a let, you can initialize some state in there.

(defn counting-button [txt]
  (let [state (reagent/atom 0)]
    (fn [txt]
      [:button.green
        {:on-click #(swap! state inc)}
        (str txt " " @state)])))

Form-3 component

React has a full lifecycle for its components, which includes methods to initialize the component and to perform effects when the component is mounted. There are lots of methods for everything that happens to a component from creation through removal from the DOM. You rarely need access to those, but when you do, Reagent gives you Form-3 components. Form-3 components are functions that return a value created by reagent/create-class.

(defn complex-component [a b c]
  (let [state (reagent/atom {})] ;; you can include state
    (reagent/create-class
      {:component-did-mount
       (fn [] (println "I mounted"))

       ;; ... other methods go here
       ;; see https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle
       ;; for a complete list

       ;; name your component for inclusion in error messages
       :display-name "complex-component"

       ;; note the keyword for this method
       :render-reagent
       (fn [a b c]
         [:div {:class c}
           [:i a] " " b])})))

See Creating Reagent Components for more explanation of the three forms.

Hiccup generates HTML

In normal React, you have two options for generating HTML in your render methods. The first and most common is to use JSX. JSX is a preprocessor to JavaScript that reads in HTML embedded in your JavaScript and turns it into normal React DOM method calls. The second method is to call those methods yourself directly, which is very verbose.

Luckily, Reagent provides a third (and possibly better) approach. Your render function can return Hiccup. Hiccup is a very common way to express HTML inside of Clojure applications. It may already be familiar to you.

Hiccup represents HTML elements as Clojure vectors. You specify the element type with a keyword. The HTML attributes go in a map. And the contents follow inside of the vector. The elements nest well. All-in-all, it is a very elegant way to embed HTML inside of Clojure.

Hiccup is more concise than using the React DOM method calls. But it is also more concise than embedded HTML since you omit the ending tag. It also has shortcuts for id and class attributes.

Reagent adds an extra feature, which is to allow you to embed Reagent components directly into the Hiccup with no extra ceremony. Simply put the component's name in the place of the element name keyword and follow it by its arguments.

;; hiccup to render deeply nested div
[:div#login-form
  [:form {:method :post :action "/login"}
    [username-field] ;; embed Reagent component (defined elsewhere)
    [password-field a b c] ;; note the arguments
    [:button {:type :submit} "Log in"]]]

Notice how easy it is to close those things. No more trouble matching tags.

Reagent Atoms

Applications need state. Part of the magic of good libraries is how that state is managed. Reagent gives us a single, flexible tool for handling state, and it's one you're probably familiar with if you've programmed in Clojure before: Atoms.

Reagent has its own Atoms. They're just like regular Clojure Atoms in every way except they do one extra thing: they keep track of Reagent components who have derefed them. Every time those Atoms change values, the component will be re-rendered.

What that means is that you have very "reactive" way to use state. You can create as many Reagent Atoms as you want. You can write any code you want to modify the states of the Atoms. And your Reagent components will re-render in response. Atoms provide one level of indirection between your app's behavior and how it is rendered.

You create a Reagent Atom by calling reagent/atom.

When will components be re-rendered?

It is very easy to understand when Reagent components will be re-rendered. In fact, there are only two circumstances under which a component will be re-rendered.

The first way is to change the arguments to the component as it is embedded in Hiccup. Here's an example. Let's say you had this Hiccup inside of a component.

[:div
  [my-component a]]

my-component is a component and a is a local variable bound to 1. Let's say that the component that returns this hiccup gets re-rendered, but this time, a is bound to 2. my-component will be re-rendered, since its arguments have changed.

But what made the outer component re-render? Let's zoom out and look at that component.

(defonce state (reagent/atom [1 2]))

(defn big-component []
  (let [[a b] @state]] ;; deref a reagent atom
    [:div
      [my-component a]]))

This big-component derefs a Reagent Atom. The Atom keeps track of all of the components that do so. And whenever that Atom changes, all of the components that derefed it up to that point are re-rendered. That's the second way to re-render a component. Since big-component doesn't have any arguments, reacting to the changing Atom must be how it got re-rendered.

React node lists and keys

React lets you return embed an array of DOM elements inside of a parent element. The elements of the array become the children of the parent. In order to support a fast way to detect changes inside of this array, React suggests that you add a unique and stable key attribute to the elements of the array.

Similarly, Reagent's Hiccup lets you embed seqs as children of other nodes. These are converted to JavaScript arrays when the Hiccup is converted to React DOM. When using seqs of elements, you need to provide keys as well.

The keys should be strings. They should be unique for that seq. And they should be stable. The key is your chance to tell React that this element is the same as the one you rendered last time with the same name.

Here's an example:

(defn student-list [students]
  [:ul
    ;; for returns a seq
    (for [student students]
      [:li {:key (:id student)} ;; stable and unique key
        [:a {:href (:url student)} (:name student)]])])

Reagent components and lazy seqs

There's one more thing. In the above example, where I created a seq, I actually created a lazy seq. This is normally okay. No problem. But sometimes it's not okay. Check out this code:

(defonce student-urls (reagent/atom {}))

(defn student-list [students]
  [:ul
    ;; for returns a lazy seq
    (for [student students]
      [:li {:key (:id student)}
        ;; deref the atom
        [:a {:href (get @student-urls (:id student))}
          (:name student)]])])

Well, this looks almost the same, except with one important difference: you are derefing a Reagent Atom. Why does this matter? Well, the elements of the lazy seq are not created right away. That means that this function will return before the body of the for is run. Since the function has already returned, the Atom doesn't know which Reagent Component it was part of and it won't know to re-render it when it changes.

The trick is to force the lazy seq to be created before you return. Just wrap the lazy seq in a doall.

(defonce student-urls (reagent/atom {}))

(defn student-list [students]
  [:ul
    ;; for returns a lazy seq
    (doall
      (for [student students]
        [:li {:key (:id student)}
          ;; deref the atom
          [:a {:href (get @student-urls (:id student))}
            (:name student)]]))])

Reagent when you have a string of HTML

Let's say you're making a component to show a blog post. The content of the blog post is a string containing HTML. So you want that HTML to render inside of the component. You might think to do something like this:

(defn blog-post 
  [:div.blog-post
    html])

But that won't work. If you just embed the string right in the component, all of the HTML tags will be escaped. You'll see the code.

React provides a way to set the HTML content of any element using a string. Here's how it looks:

(defn blog-post 
  [:div.blog-post {:dangerouslySetInnerHTML {:__html html}}])

It's crazy. It's longwinded. It's verbose and a pain to type. And that's on purpose. They don't want you doing this by accident. You see, React is there to make the DOM easier to use. And one of the things that makes the DOM hard is cross-site scripting attacks. If you render comments from blog readers, and that content is not sanitized, you could render some HTML with a <script> tag in it that could do some nasty stuff to other readers. So the default behavior of components is safe: escape the string. Of course, sometimes you do trust the data and the data is in HTML, so you want to just embed it. That's what this is for. Make sure you really do trust that HTML (either by its source or it has been sanitized) before you use this.

Getting at the DOM node itself

React provides a great level of indirection from the DOM itself. You no longer have to do everything by setting properties and adding and removing children. However, there are times when you really do want the individual DOM nodes. Some APIs, for example, want the DOM node. The FormData API takes in the <form> node. Or if you need to draw on a <canvas>, you need to get a reference to the actual DOM node.

React knows this is important so it has provided a really nice facility to get the DOM nodes of any element you render. It's called refs. Reagent makes it super easy to use. You'll want a Form-2 or Form-3 component for this, because you'll want local state. Just add the :ref property to the elements you want to keep track of.

(defn form-canvas []
  (let [s (reagent/atom {})]
    (fn []
      [:div
        [:form {:ref #(swap! s assoc :form %)}
          [:input {:type :text :name "first-name"}]
          [:input {:type :hidden :name "token" :value "Hello!"}]]
        [:canvas {:ref #(swap! s assoc :canvas %)}]])))

So what's going on here? In this component, we're rendering a form and a canvas inside of a div. We want to keep a reference to the form and canvas elements themselves so we can use them (which I've chosen not to show, just for brevity). And notice we've added :ref attributes. These take a function of one argument. That argument will be the DOM node itself, or nil in cases where the node doesn't exist. We save it away safely in the state atom we're keeping.

Now, I've said that it's either the DOM node or nil. That means you'll have to check when you're ready to use it. When will it be nil? Either before the DOM node has been added to the DOM (before mounting) or after the DOM node has been removed from the DOM (after unmounting). This mechanism is nice because it makes cleanup easier. React can call your ref function with nil and references are cleaned up.

You can use those nodes you've saved however you like. Just be sure to check if they're nil before you call methods on them.