ClojureScript + Reagent Tutorial with Code Examples

Build your SPA in ClojureScript!

Master Reagent and Re-frame with my ClojureScript Frontend Signature Course.

  • 3 frontend modules
  • 72 detailed lessons
  • 19 hours of video
ClojureScript Frontend: An Eric Normand Signature Course

This guide will teach you all of the things that Re-frame gives you that we use in Re-frame. Re-frame is built on Reagent and uses many of the concepts from Reagent as is. Reagent is a thin but high-leverage wrapper around React.

In this guide, you will learn the three ways to create components, the details of Hiccup (including shortcuts), how to get access to the real DOM nodes, how to create atoms and react to changes in them, and how to understand re-rendering.

Table of Contents

More posts in this Re-frame Series

What is 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.

Reagent 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)] ;; state is accessible in the render function
    (fn [txt]
      [:button.green
        {:on-click #(swap! state inc)}
        (str txt " " @state)])))

The outer function will be called once, then the inner function will be called for each render. The two functions should take the same arguments.

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

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

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

:display-name gives the component a name which Reagent will use when printing debugging messages. It's useful to give it a name.

React defines nine different lifecycle methods. Each method has its own complex usage. Fortunately, Reagent makes most of the methods unnecessary. There are really only four you will ever need to use.

:component-did-mount is called right after the component is added to the DOM. This is the first time you will have access to the actual DOM element connected with this component. The method takes one argument: the component itself. Call reagent.core/dom-node on it to get the DOM node. Implement this method to set up DOM nodes for external components such as HTML canvases.

:component-did-mount
(fn [comp]
  (let [node (reagent/dom-node comp)
        ;; some canvas stuff
        ctx (.getContext node "2d")]
    (.fillRect ctx 10 10 10 10)))

:reagent-render is the render method you are used to from Form-1 and Form-2. You simply need to give it a name in the create-class function. This method is required.

:component-did-update is called just after re-rendering. That means the DOM nodes have potentially just been recreated by the Virtual DOM algorithm. If you are doing something special with :component-did-mount, you may need to do it again here. You may also want to make some changes based on changes to the local state.

:component-did-update
(fn [comp]
  (let [node (reagent/dom-node comp)
        ctx (.getContext node "2d")]
    (.fillRect ctx 10 10 10 10)))

:component-will-unmount is called just before the component is unmounted from the DOM. You still have access to the DOM node. This is your last chance to clean up anything you might need to before your component is gone forever. It is rare that you will want to implement this method.

I go over each of them in more detail here.

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

Note: in general, Reagent will convert camelCase (initial capitals) into kebab-case (lowercase with hyphens), which is more common in Clojure. If you look up the documentation for the React lifecycle methods, they will be noted in camelCase. You can mentally translate them to kebab-case.

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.

Basic HTML

The basics of Hiccup are fairly easy. If you have an HTML tag like

<div></div>

That becomes

[:div]
  1. Every HTML tag becomes a Clojure Vector. There's no need for a closing tag because the Vector is closed with the ] (closing square bracket).
  2. The tag name, div, becomes a keyword with the same name, :div.

HTML nests tags inside of other tags, so we'll need a way to do that.

<div>
  <h1>Hello!</h1>
  <p>This is my message.</p>
</div>

Becomes:

[:div
 [:h1 "Hello!"]
 [:p "This is my message."]]
  1. The h1 is nested in the div. So the :h1 vector is an element of the :div vector. It has to come after the tag name. You can have multiple children.
  2. Text is represented as a ClojureScript String.

Luckily, React and Reagent handle nil children gracefully. That lets you do some convenient patterns like this:

[:div
  (when title [:h1 title])
  (when msg [:p msg])]

when will return nil if the test is falsy. If both title and msg are missing, you get this hiccup:

[:div nil nil]

Reagent will ignore nils and render it:

<div></div>

Okay, but what about attributes? HTML tags usually have attributes, like this:

<div class="container">
  <h1 id="greeting">Hello!</h1>
  <p>This is my message</p>
</div>

In Hiccup, the attributes are a hash map that comes right after the tag name. This becomes:

[:div {:class "container"}
 [:h1 {:id "greeting"}
  "Hello!"]
 [:p "This is my message"]]

The attributes hash map is optional. And note that the attribute names are kebab-case in Reagent. If you look through the React documentation, they are in camelCase. Also: class is a reserved keyword in JavaScript, so React refers to the className attribute. In Reagent, that is correctly shortened back to :class.

Hiccup has some special rules for some attributes and their values that are super convenient when building components.

Style attributes

In HTML, if you want to put an inline style in a tag, you do so with an attribute. The value is a string of valid CSS.

<div style="margin-left:10px; width:15%">
  <p>Some text</p>
</div>

You're embedding a string in there that has to be correct CSS. But when you're generating these attributes programmatically, you don't want to have to concatenate strings properly using complex logic. You want it broken out into something easier to work within your language. Luckily, React did break the style attribute out. In React, you use a JavaScript object. The keys are the CSS property names, and the values are the CSS values as strings. Numbers also work for some properties. They become numbers of pixels. For instance, 200 becomes 200px in CSS.

Reagent builds on that and lets you use a ClojureScript map to represent inline styles. The same rules apply: the name of the CSS property is the key and the value of the CSS property is the value. Here's what the above div with inline styles will look like:

[:div {:style {:margin-left 10
               :width "15%"}}
 [:p "Some text"]]

Notice we're using a number, 10, because it's 10px. But we have to use the string "15%" because the units are not px. Also, notice that we can use the proper CSS name, margin-left, which is not valid JavaScript because of the -. Reagent handles that for us!

React also gives us a few extra shortcuts that really help when generating these data structures.

nil and false attribute values

If the value of an attribute (any attribute, including styles) is nil or false, the attribute is not included in the tag. This is very useful when you're building something conditionally. Here's an example:

(defn message [txt urgent?]
  [:div {:style {:color (when urgent? "red")}}
    txt])

When urgent? is true, we want the message text to be red. But when it's not true, we don't want to change the color at all. This code will do just that, because the when expression will be nil when urgent? is false.

Some HTML attributes don't take on values. They're just supposed to be there or not. For instance, if you want a checkbox to be checked, you do:

<input type="checkbox" checked />

The checked attribute doesn't need a value. But in our representation, we're using a map. So every key needs a value! Reagent and React are smart. You can use true and false as values. If you want the checkbox checked, give the checked attribute a value of true, otherwise, false. No need to worry about having it or not.

(defn checkbox [name checked?]
  [:input {:type :checkbox
           :checked (boolean checked?)
           :name name}])

Other values

ClojureScript values for attributes have their own semantics. Here's how they work:

  • Booleans: false means ignore the attribute, true means show it with no value.
  • Strings: passed along as-is
  • Numbers: if they're the values of styles, they're px; otherwise, turned into Strings
  • Keywords and Symbols: their names (Strings) are passed; :a/b gets turned into "b"
  • Maps: Turned into JavaScript Objects and passed to React.
  • Other collections: Turned into equivalent JS values and passed along.
  • Functions: passed along
  • nil: ignore the attribute

class pattern

A lot of HTML tags have multiple classes. Some of those classes are permanent, meaning they don't change. But some of them will change. For example, you might want to highlight the active element in a list view. So you add the active class, which gives is a blue glow. But then as soon as it's not active, you remove that class. This can get messy since you're often concatenating the strings manually.

My solution is to write a small helper function that will concatenate them for me. It lets me write the logic inline. I call it cs because it's short. It stands for classes.

(defn cs [& names]
  (str/join " " (filter identity names)))

The filter gets rid of nil and false names. Then I join them up with a space between them. I use it like this:

(defn element [txt active? hidden?]
 [:div {:class (cs "element"
                   (when active? "active")
                   (when hidden? "hidden"))}
  txt])

Perfect? I don't think so. But it works :) I'd love to hear what you do.

id and class shortcut

Reagent also has shortcuts for id and class attributes. These can be useful. Here's how it works.

Instead of setting the :id attribute, you can simply put a # symbol after the tag name. It's supposed to look like CSS selector syntax.

[:div#main-content
 ....]

That creates HTML like this:

<div id="main-content">...</div>

If you don't have any other attributes, it's way shorter and easier to read.

You can do something similar with the :class attribute. This time, use a . like in CSS:

[:button.btn.btn-primary ...]

Notice that that creates two classes:

<button class="btn btn-primary">...</button>

Also very convenient. You can use both the id and class shortcuts, but you should always put the id one first.

And the shortcut class does work with the class attribute! Consider putting the permanent classes in the shortcut and the temporary classes in the attribute.

Nested tag shortcut

Something that Reagent Hiccup does that I don't use very much is to allow you to deeply nest tags with a single vector. You use something quite like the CSS selector for directly nested tags. It seems like it could be very useful for some of those CSS frameworks that like to wrap everything up in divs with classes:

[:div.wrapper>div.inner
  "Hello!"]

That creates this structure:

<div class="wrapper">
  <div class="inner">Hello!</div>
</div>

It's great when that's all you need. But note that you can't set attributes on the outer elements, only the inner one.

React DOM Events

React defines its own events which wrap the regular DOM events provided by the browsers. Each browser is different, so this is really good for cross-platform coding. What's more, React's events are more like how you'd expect things to work. For instance, the onchange HTML Event on a text input fires when the element loses focus, not when you're typing in it. React fixes this problem, and more.

In React, you set the event handlers with attributes. The values should be functions. The functions take a single argument, which is all the information about the event. I usually call it e because it's short.

All React event objects have some common properties and some properties specific to them. The React docs are the best source of information on all of the possible events. It is complete and up-to-date. Just remember that Reagent will use kebab-case while the React docs use camelCase.

Just as an example, here's how we can respond to the :on-click event of a button:

[:button.btn.btn-primary
  {:on-click (fn [e] (js/console.log "Hello!!"))}
  "Click me!"]

Now a word about default handlers and event propagation. The HTML event model is still in effect. These are just HTML events with a small wrapper object around them.

You should be familiar with the event propagation model of the DOM. Basically, when an event fires on a DOM node, the handler on that node is called. Then the handler is called on its parent. Then on its parent, all the way up to the top of the DOM tree. You can stop that behavior in any handler by calling (.stopPropagation e) (see stopPropagation).

Also, many elements have default behaviors. For instance, a submit button will submit the form it is located in when clicked. You can prevent the default behavior by calling (.preventDefault e) inside the handler. See preventDefault.

Sub-components

Reagent adds an extra feature to Hiccup, 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.

Embedding React components from JavaScript

There's another trick you can do with Reagent Hiccup. Sometimes you have a React component, let's say it's written in JavaScript, and you want to embed it in the Hiccup. There's a bit of translation that Reagent will have to do. However, Reagent makes it easy. Use the :> directive instead of an HTML tag name or a Reagent component.

[:> js/nativeJSComponent {:title "Hello" :otherProp "World"}]

After the :> directive, you put the React component as it is named in JavaScript. Then you follow it with the React component's props. Note that the Clojure map is converted into the proper JavaScript object, but camelCase should be preserved.

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 [html]
  [: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 [html]
  [: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 make 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.

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. It's not strictly required, but it is good practice. You'll see lots of warnings if you don't add a key.

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)]])])

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. It's lazy, so the elements are only created as they are accessed. 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)]]))])

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 a local state. Just add the :ref property to the elements you want to keep track of.

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

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.

Forms

There is one very important pattern to use when building forms. In single-page apps, you often don't use the default form behavior of posting to the server. Instead, you capture the form submission and do something yourself, like your own AJAX request. Whatever you do, you will want to get the values out of the form, usually in a centralized place. Also, you want the form to be interactive—it's not just a box to fill in. Instead, you give feedback to the user as they type. You will want to centralize the local component state. Here's the pattern.

First, we make a Form-2 component that takes the default values in the form.

(defn contact-form [first-name last-name email-address message]

Then we create an Atom to store local state. We'll pass in the initial values.

(defn contact-form [first-name last-name email-address message]
  (let [s (reagent/atom {:first-name first-name
                         :last-name  last-name
                         :email email-address
                         :message message})]

Now we return the inner (render) function. Notice that we don't repeat the arguments. We don't want changes to those arguments to change the form after we have constructed it.

(defn contact-form [first-name last-name email-address message]
  (let [s (reagent/atom {:first-name first-name
                         :last-name  last-name
                         :email email-address
                         :message message})]
    (fn []

Your form shouldn't do the default submission behavior. So prevent the default in the handler and do whatever you need. The current values you want will be in the Atom.

(defn contact-form [first-name last-name email-address message]
  (let [s (reagent/atom {:first-name first-name
                         :last-name  last-name
                         :email email-address
                         :message message})]
    (fn []
      [:form {:on-submit (fn [e]
                           (.preventDefault e)
                           ... ;; do something with the state @s
                           )}

Now we create the input fields. Let's start with just one. We want to lock the value of the input field to the Atom. To lock it, we need to go both ways: if the Atom changes, we change the field. If the field changes, we change the Atom. We set the value attribute based on the contents of the Atom. And our :on-change handler swap!s into the Atom. We have to dig into the event, getting the .-target (the DOM element), then it's .-value (the current text).

(defn contact-form [first-name last-name email-address message]
  (let [s (reagent/atom {:first-name first-name
                         :last-name  last-name
                         :email email-address
                         :message message})]
    (fn []
      [:form {:on-submit (fn [e]
                           (.preventDefault e)
                           ... ;; do something with the state @s
                           )}
        [:input {:type :text :name :first-name
                 :value (:first-name @s)
                 :on-change (fn [e]
                              (swap! s :first-name (-> e .-target .-value)))}]

Now, the value at :first-name in the Atom will be locked to the input field. Let's do the other fields:

(defn contact-form [first-name last-name email-address message]
  (let [s (reagent/atom {:first-name first-name
                         :last-name  last-name
                         :email email-address
                         :message message})]
    (fn []
      [:form {:on-submit (fn [e]
                           (.preventDefault e)
                           ... ;; do something with the state @s
                           )}
        [:input {:type :text :name :first-name
                 :value (:first-name @s)
                 :on-change (fn [e]
                              (swap! s :first-name (-> e .-target .-value)))}]
        [:input {:type :text :name :last-name
                 :value (:last-name @s)
                 :on-change (fn [e]
                              (swap! s :last-name (-> e .-target .-value)))}]
        [:input {:type :email :name :email
                 :value (:email @s)
                 :on-change (fn [e]
                              (swap! s :email (-> e .-target .-value)))}]
        [:textarea {:name :message
                    :on-change (fn [e]
                                 (swap! s :message (-> e .-target .-value)))}
          (:message @s)]])))

The only difference on the last one is that it's a textarea, so the value is set as a child instead of the value attribute.

What I like to do is store the state of all inputs at the level of the form. So there's a component for the form that controls everything. Why? For a number of reasons. The first is that when I do validation, I find that I often need access to the values in multiple fields. And for the sake of feedback to the user, I like to recalculate and display the validation with every keystroke. Also, when you're finally ready to submit the form, you have everything you need to build up the AJAX request or whatever it is you are doing with it. I've tried breaking up the elements into components, but I have trouble re-centralizing the values.

You can get more sophisticated than the example above. For instance, you might not want to show the validation until after they've blurred the field. You can keep track of "dirty" input fields inside the state as fields get blurred.

So that's the locking pattern with fields, where they get locked to an Atom. And there's my recommendation to build the component at the level of the form so you can centralize the state. And finally, you can add onto this recommendation to make more sophisticated interactions.

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.

There are four functions you will need to use the Atom API.

You create a Reagent Atom by calling reagent/atom.

After that, you can use reset! to set the current value of the atom regardless of the current value.

(reset! state 10) ;; set state to 10

swap! is the most common way to change the value of an Atom. It calculates a new value based on the current value, using a function you pass it.

(swap! state + 12) ;; add 12 to current state

We've already talked about deref. It returns the current value of the Atom. There's a reader shortcut using the @ symbol as a prefix. The special thing about deref for Reagent Atoms is that the component doing the derefing will be re-rendered every time the Atom's value changes.

(deref state) ;; returns current value
@state ;; equivalent to above

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 in its render function. 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.

WARNING

You should only deref (or use @) inside the render function, not in the outer scope of a Form-2 component. The following is wrong:

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

(defn big-component-no-re-render []
  (let [[a b] @state]] ;; deref a reagent atom
    (fn [] ;; form-2 component returns a render fn
      [:div
        [my-component a]])))

This component will not re-render when state changes. Why? Because the deref does not occur inside the render function. Instead, it's only done once, when the component is initialized. This is a common mistake. Derefing should always happen in the render function.

So here's the trick: look for @ signs and arguments inside the render function. If any of those change, the component will be re-rendered.

More posts in this Re-frame Series

Build your SPA in ClojureScript!

Master Reagent and Re-frame with my ClojureScript Frontend Signature Course.

  • 3 frontend modules
  • 72 detailed lessons
  • 19 hours of video
ClojureScript Frontend: An Eric Normand Signature Course