Reagent & Hiccup

I'm writing this post because hiccup in the context of Reagent confused me and I wanted to work through some of the confusion.

Things that confused me: I apparently don't need to import a library, modify my build tools or do anything and I just get to write hiccup for free? How is this possible? Also, what is hiccup and is it the same everywhere?

In React (JavaScript) you primarily write your HTML markup with JSX. In Reagent (a ClojureScript wrapper around React) we write our HTML markup with something called hiccup.

Here is a React hello world written in JS uxing jsx:

ReactDOM.render(
  <h1 className="welcome">Hello, world!</h1>, // <-- JSX
  document.getElementById('root')
);

Here is the same React hello world written in Reagent using hiccup:

(reagent.dom/render
  [:h1 {:class "welcome"} "Hello, world!"] ; <-- Hiccup
  (.. js/document (getElementById  "root")))

Let's pull the JSX and hiccup out so we can see them clearly:

// JSX
<h1 className="welcome">Hello, world!</h1>


// hiccup
[:h1 {:class "welcome"} "Hello, world!"]

For fun, here is some more hiccup:

[:ul {:class "list"}
  [:li {:class "list-item"} "Item 1"]
  [:li {:class "list-item"} "Item 2"]
  [:li {:class "list-item"} "Item 3"]]

The rest of this post is going to dig into the questions asked in the introductory paragraph.

Reagent Hiccup

Let's return to the original reagent code snippet we started with:

(reagent.dom/render
  [:h1 {:class "welcome"} "Hello, world!"] ; <-- hiccup
  (.. js/document (getElementById  "root")))

On line 2 we have a Clojure vector. Don't overthink it. It's just a vector. However, because of the order and type of the arguments, it becomes hiccup.

To be considered valid Reagent Hiccup, the vector you pass to Reagent needs to take on one of the following shapes:

[tag]

; => [:h1]

[tag attributes]

; => [:h1 {:class "welcome"}]

[tag children]

; => [:h1 "Hello world!"]

[tag attributes children]

; => [:h1 {:class "welcome"} "Hello world!"]

Here is another way to break it down:

  • tag
    • A keyword (:h1) or symbol (hi)
  • attributes
    • A map {:class "welcome"}
  • children
    • A string ("Hello world!"), vector ([:p "hi"]) or symbol (hi)

How does Reagent know what do with hiccup? The reagent.dom/render accepts either Reagent Hiccup or a React Element as the first argument. So by providing a vector, Reagent automatically treats it like Reagent Hiccup.

Thus, if we were to pass something that's not actually Reagent Hiccup, Reagent is kind enough to throw a JavaScript assertion error in the browser console letting us know what went wrong.

Okay, so Reagent can just accept hiccup. How does Reagent understand Hiccup and know what to do with it? We didn't import a library or add a plugin to our build tools?

The answer is that Reagent comes with a Hiccup compiler built-in which converts Reagent's hiccup to React Elements.

Reagent Hiccup to React Element

The process begins by Reagent passing the component given to reagent.dom/render to a function called create-class.

create-class has other jobs aside from handling Hiccup, but one of it's jobs is to compile Reagent Hiccup to React.createElement calls. This step is handled by the as-element function.

as-element accepts Reagent Hiccup like this:

[:h1 {:class "welcome"} "Hello, world!"]

Compiles it to React.createElement function calls like this:

React.createElement(
  "h1",
  {className: 'welcome'},
  "Hello, world!"
);

The above is given to React which actually runs the React.createElement calls turning them into React Elements like this:

{
  type: "h1",
  props: {
    className: 'greeting',
    children: "Hello, world!"
  }
};

which ultimatley gets turned into HTML

<h1 class="welcome">Hello, world!</h1>

Understanding that everything is turned into React.createElement calls you might be asking, "Could we just use React.createElement and not use Reagent Hiccup?". The answer? Yup!

Reagent Without Hiccup

Similar to React and JSX, you can use plain old React.createElement to create Reagent Components. For example, where we wrote the original example as:

(reagent.dom/render
  [:h1 {:class "welcome"} "Hello, world!"]
  (.. js/document (getElementById  "root")))

we could also use reagent.core/create-element to write a Reagent component like this:

(reagent.dom/render
  (reagent.core/create-element
    "h1"
    #js{:className "welcome"}
    "Hello, world!")
  (.. js/document (getElementById  "root")))

Thus, the following are equivalent

; hiccup
[:h1 {:class "welcome"} "Hello, world!"]

; reagent function
(reagent.core/create-element
  "h1"
  #js{:className "welcome"}
  "Hello, world!")

Understanding this, are there any reasons to use reagent.core/create-element over Reagent Hiccup?

Why Reagent Hiccup?

From a technical perspective, you can argue that there is less work being done if you don't use hiccup because you don't have to convert hiccup to createElement calls. However, i'm not sure this optimization would be worth it. Especially because you could just change the implementation of hiccup to be run at compile time and see the performance improvements and still have the advantage of writing your components in hiccup.

From a developer experience perspective, you might suggest that using reagent.core/create-element over hiccup initially feels easier to teach new developers how to create components in React/Reagent. However, this is subjective and I could argue that hiccup is far friendlier and because of its uniquity is a better tool to rely on.

Some more reasons we use Hiccup in Reagent is similar to the reasons for using JSX in React.

  • Separation of concerns: Separate by component vs. technology
  • Accessibility: easier to read and write than React.createElement
  • Expressivity: it's a Clojure data structure, so we have the full power of Clojure

The second point, Accessibility, is particularly interesting. One of the things I mean by this is that because Hiccup is a popular DSL in Clojure land, and not specific to Reagent, it can be quickly be adopted by developers already familiar with Hiccup.

Hiccup and Clojure

As I mentioned at the top, there are many flavours of hiccup. hiccup is not a Reagent thing, but a DSL which was adapted from a server-side tool called Hiccup.

hiccup was introduced by James Reeves and has become a standard DSL for Clojure developers looking to represent HTML in Clojure. This means that there are many 3rd party libraries which allow you to write Hiccup in your app even if you aren't using Reagent. For example, the following are all examples of popular Hiccup libraries.

And I think this is the beginning of some of the confusion when it comes to hiccup and Reagent.

When one starts to learn ClojureScript, they often start with Clojure. The Clojure guides, references and libraries are generally oriented towards Clojure. While this is understanable, it can create a challenge when trying to figure out how to write HTML in Clojure.

My story was that I started with Clojure building a little MPA with hiccup. I found my library of choice and made stuff appear on the screen by calling the hiccup library I chose. Then I tried to make the same thing happen in Reagent and noticed that I didn't need a library or to call to anything and it all just worked. I eventually learned what was happening, hence this post, but it was irritating at first to not get what was really going on.

Conclusion

The overarching point is that hiccup is a common way of writing HTML is Clojure. Unlike JSX, which for a long while was a React only thing, Hiccup is not specific to Reagent/React. So, this point is meant to provide the context.

Want to learn more about hiccup? Tonsky's article on hiccup is pretty great.