<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[blog.404.taipei]]></title><description><![CDATA[Knowledge sharing, Clojure and other. Mostly about front-end development.]]></description><link>https://blog.404.taipei</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1736195258093/a823ece6-f95e-45af-9234-6b620c2ed111.png</url><title>blog.404.taipei</title><link>https://blog.404.taipei</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 09:55:21 GMT</lastBuildDate><atom:link href="https://blog.404.taipei/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[What's in a component]]></title><description><![CDATA[What’s in a component? That which implemented in any other way would fell as reusable


The complete source code displayed in this article can be found in Vrac’s repository.
In the last post “Making HTML reactive using Signaali“, we hooked up our cod...]]></description><link>https://blog.404.taipei/whats-in-a-component</link><guid isPermaLink="true">https://blog.404.taipei/whats-in-a-component</guid><category><![CDATA[Clojure]]></category><category><![CDATA[ClojureScript]]></category><category><![CDATA[Signaali]]></category><category><![CDATA[Vrac]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Vincent Cantin (green-coder)]]></dc:creator><pubDate>Fri, 10 Jan 2025 08:02:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736495928668/d852dd78-0306-4bb1-929a-0b0bd4506469.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>What’s in a component? That which implemented in any other way would fell as reusable</p>
<p><img src="https://a.slack-edge.com/production-standard-emoji-assets/14.0/apple-medium/1f339@2x.png" alt=":rose:" /></p>
</blockquote>
<p>The complete source code displayed in this article can be found in <a target="_blank" href="https://github.com/metosin/vrac/tree/article/002-whats-in-a-component/blog-posts/002-whats-in-a-component">Vrac’s repository.</a></p>
<p>In the last post “<a target="_blank" href="https://blog.404.taipei/making-html-reactive-using-signaali">Making HTML reactive using Signaali</a>“, we hooked up our code to HTML elements already existing in an HTML page. That’s not how modern web development work, nowadays we create them within our program and refer to them from there.</p>
<p>So, let’s represent HTML in our program. But wait .. which format should we choose? Hiccup is using Clojure vectors for both HTML and data, sometimes causing ambiguity for people reading the source code and security issues at runtime. <a target="_blank" href="https://github.com/pitch-io/uix">UIx</a> is using a syntax which is not ambiguous, so let’s reuse it!</p>
<p>We go from:</p>
<pre><code class="lang-xml">    <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Developing Vrac from scratch - components<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This demonstrates how Vrac is implemented.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Reactive counter<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        Counter value: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"counter-element"</span>&gt;</span>n/a<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"inc-counter-button"</span>&gt;</span>Increment<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"reset-counter-button"</span>&gt;</span>Reset<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Lazy effects<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        The effects are lazy, the user decides when to re-run them.
        Click the button to update the DOM.
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"run-effects-button"</span>&gt;</span>Run effects<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
</code></pre>
<p>and convert it to:</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defn</span> <span class="hljs-title">my-html</span> []
  ($ <span class="hljs-symbol">:main</span>
     ($ <span class="hljs-symbol">:h1</span> <span class="hljs-string">"Developing Vrac from scratch - components"</span>)
     ($ <span class="hljs-symbol">:p</span> <span class="hljs-string">"This demonstrates how Vrac is implemented."</span>)
     ($ <span class="hljs-symbol">:h2</span> <span class="hljs-string">"Reactive counter"</span>)
     ($ <span class="hljs-symbol">:div</span> <span class="hljs-string">"Counter value:"</span> ($ <span class="hljs-symbol">:span#counter-element</span> <span class="hljs-string">"n/a"</span>)
        ($ <span class="hljs-symbol">:div</span>
           ($ <span class="hljs-symbol">:button#inc-counter-button</span> <span class="hljs-string">"Increment"</span>)
           ($ <span class="hljs-symbol">:button#reset-counter-button</span> <span class="hljs-string">"Reset"</span>)))
     ($ <span class="hljs-symbol">:h2</span> <span class="hljs-string">"Lazy effects"</span>)
     ($ <span class="hljs-symbol">:div</span> <span class="hljs-string">"The effects are lazy, the user decides when to re-run them. Click the button to update the DOM."</span>
        ($ <span class="hljs-symbol">:div</span>
           ($ <span class="hljs-symbol">:button#run-effects-button</span> <span class="hljs-string">"Run effects"</span>)))))
</code></pre>
<p>There are a few changes to do:</p>
<ul>
<li><p>We want to stop referring to HTML elements by id and instead refer to them contextually or via Clojure symbols.</p>
</li>
<li><p>The <code>:span</code> element looks a bit useless now that we don’t use the <code>id</code>, let’s remove it and directly refer to a text node instead.</p>
</li>
<li><p>We want to have a Signaali state for the counter.</p>
</li>
<li><p>We want to avoid verbosity for the effect which updates a counter text node, so we want it to be implicit.</p>
</li>
<li><p>We want to add event listeners on the buttons.</p>
</li>
</ul>
<p>With a bit of magic, it could look like:</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defn</span> <span class="hljs-title">my-html</span> []
  ($ <span class="hljs-symbol">:main</span>
     ($ <span class="hljs-symbol">:h1</span> <span class="hljs-string">"Developing Vrac from scratch - components"</span>)
     ($ <span class="hljs-symbol">:p</span> <span class="hljs-string">"This demonstrates how Vrac is implemented."</span>)
     ($ <span class="hljs-symbol">:h2</span> <span class="hljs-string">"Reactive counter"</span>)
     (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [counter-state (<span class="hljs-name">sr/create-state</span> <span class="hljs-number">0</span>)]
       ($ <span class="hljs-symbol">:div</span> <span class="hljs-string">"Counter value:"</span> counter-state
          ($ <span class="hljs-symbol">:div</span>
             ($ <span class="hljs-symbol">:button</span> {<span class="hljs-symbol">:on-click</span> #(<span class="hljs-name"><span class="hljs-builtin-name">swap!</span></span> counter-state inc)} <span class="hljs-string">"Increment"</span>)
             ($ <span class="hljs-symbol">:button</span> {<span class="hljs-symbol">:on-click</span> #(<span class="hljs-name"><span class="hljs-builtin-name">reset!</span></span> counter-state <span class="hljs-number">0</span>)} <span class="hljs-string">"Reset"</span>))))
     ($ <span class="hljs-symbol">:h2</span> <span class="hljs-string">"Lazy effects"</span>)
     ($ <span class="hljs-symbol">:div</span> <span class="hljs-string">"The effects are lazy, the user decides when to re-run them. Click the button to update the DOM."</span>
        ($ <span class="hljs-symbol">:div</span>
           ($ <span class="hljs-symbol">:button</span> {<span class="hljs-symbol">:on-click</span> #(<span class="hljs-name">sr/re-run-stale-effectful-nodes</span>)} <span class="hljs-string">"Run effects"</span>)))))
</code></pre>
<p>The function <code>my-html</code> starts to look like a real <s>React</s> component now. But what is a component alone? We should have more! How do we compose them? Our components are functions, so we compose them by calling those functions — naturally.</p>
<h2 id="heading-you-can-count-on-me-like-1-2-3-ill-be-there">You can count on me like 1-2-3, I’ll be there …</h2>
<p>Let’s say that we want more counters in our small experiment — 3 of them.</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defn</span> <span class="hljs-title">my-counter</span> [counter-state]
  ($ <span class="hljs-symbol">:div</span> <span class="hljs-string">"Counter value:"</span> counter-state
     ($ <span class="hljs-symbol">:div</span>
        ($ <span class="hljs-symbol">:button</span> {<span class="hljs-symbol">:on-click</span> #(<span class="hljs-name"><span class="hljs-builtin-name">swap!</span></span> counter-state inc)} <span class="hljs-string">"Increment"</span>)
        ($ <span class="hljs-symbol">:button</span> {<span class="hljs-symbol">:on-click</span> #(<span class="hljs-name"><span class="hljs-builtin-name">reset!</span></span> counter-state <span class="hljs-number">0</span>)} <span class="hljs-string">"Reset"</span>))))

(<span class="hljs-keyword">defn</span> <span class="hljs-title">my-html</span> [nb-counters] <span class="hljs-comment">;; nb-counters is 3</span>
  ($ <span class="hljs-symbol">:main</span>
     ($ <span class="hljs-symbol">:h1</span> <span class="hljs-string">"Giving shape to components"</span>)
     ($ <span class="hljs-symbol">:p</span> <span class="hljs-string">"This demonstrates how Vrac is implemented."</span>)
     ($ <span class="hljs-symbol">:h2</span> <span class="hljs-string">"Reactive counters"</span>)
     (<span class="hljs-name"><span class="hljs-builtin-name">for</span></span> [i (<span class="hljs-name"><span class="hljs-builtin-name">range</span></span> nb-counters)]
       (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [counter-state (<span class="hljs-name">sr/create-state</span> (<span class="hljs-name"><span class="hljs-builtin-name">*</span></span> i <span class="hljs-number">100</span>))]
         (<span class="hljs-name">my-counter</span> counter-state)))
     ($ <span class="hljs-symbol">:h2</span> <span class="hljs-string">"Lazy effects"</span>)
     ($ <span class="hljs-symbol">:div</span> <span class="hljs-string">"The effects are lazy, the user decides when to re-run them. Click the button to update the DOM."</span>
        ($ <span class="hljs-symbol">:div</span>
           ($ <span class="hljs-symbol">:button</span> {<span class="hljs-symbol">:on-click</span> #(<span class="hljs-name">sr/re-run-stale-effectful-nodes</span>)} <span class="hljs-string">"Run effects"</span>)))))
</code></pre>
<h2 id="heading-the-function">The <code>$</code> function</h2>
<p>The obvious first question is: what kind value should it return?</p>
<p>Well, we chose this function mainly to remove any ambiguity. But we want it to work similarly to Hiccup to represent an HTML structure which can be transformed later.</p>
<p>Because the <code>($ :div ,,,)</code> expressions look different from the user’s perspective and because it will contain a few new types of values, let’s give this structure a new name: “Vrac-Hiccup”, shortened as “Vcup”.</p>
<p>“Vcup” is made of either:</p>
<ul>
<li><p>a VcupNode which is a node of children Vcup elements, or</p>
</li>
<li><p>a Vcup leaf value — text, reactive node, etc …</p>
</li>
</ul>
<p>.. and because we use types and not Clojure data structures, there is always enough space for more types of node or leaf values.</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defrecord</span> <span class="hljs-title">VcupNode</span> [tag children])

(<span class="hljs-keyword">defn</span> $ [tag &amp; children]
  (<span class="hljs-name">VcupNode.</span> tag children))
</code></pre>
<h2 id="heading-rendering-the-root-component-within-the-html-page">Rendering the root component within the HTML page</h2>
<p>With the page’s HTML being handled by the app, <code>index.html</code> now looks like a standard SPA’s <code>index.html</code>:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width,initial-scale=1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Making HTML reactive using own HTML library<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/js/main.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>The entry point and reloading code of the app becomes:</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defn</span> ^<span class="hljs-symbol">:dev/after-load</span> <span class="hljs-title">setup!</span> []
  (<span class="hljs-name">render</span> (<span class="hljs-name">js/document.getElementById</span> <span class="hljs-string">"app"</span>)
          (<span class="hljs-name">my-html</span> <span class="hljs-number">3</span>)))

(<span class="hljs-keyword">defn</span> ^<span class="hljs-symbol">:dev/before-load</span> <span class="hljs-title">shutdown!</span> []
  (<span class="hljs-name">dispose-render-effects</span>))

(<span class="hljs-keyword">defn</span> <span class="hljs-title">start-app</span> []
  (<span class="hljs-name">setup!</span>))
</code></pre>
<p>Starting from here in this article, all the functions belong to the Vrac library and not the example. Let’s dive in.</p>
<p>The render function:</p>
<ol>
<li><p>processes the vcup returned by the root component, uses <code>process-vcup</code> to turn it into a DOM elements + some reactive effects,</p>
</li>
<li><p>sets the DOM elements as children of the element defined in <code>index.html</code>,</p>
</li>
<li><p>then runs the effects for the first time, enabling them to find their dependencies and react to them in the future.</p>
</li>
</ol>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defn</span> <span class="hljs-title">render</span> [<span class="hljs-comment">^js/Element</span> parent-element vcup]
  (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [{<span class="hljs-symbol">:keys</span> [effects elements]} (<span class="hljs-name">process-vcup</span> vcup)]
    <span class="hljs-comment">;; Set all the elements as children of parent-element</span>
    (<span class="hljs-name">.apply</span> (<span class="hljs-name">.-replaceChildren</span> parent-element) parent-element (<span class="hljs-name"><span class="hljs-builtin-name">to-array</span></span> elements))
    <span class="hljs-comment">;; Run all the effects</span>
    (<span class="hljs-name">run!</span> sr/run-if-needed effects)))

(<span class="hljs-keyword">defn</span> <span class="hljs-title">dispose-render-effects</span> []
  <span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> dispose all the effects used for the rendering and the DOM updates.</span>
  <span class="hljs-comment">;; In this article, we will skip this step.</span>
  ,)
</code></pre>
<h2 id="heading-implementing-the-process-vcup-function">Implementing the <code>process-vcup</code> function</h2>
<p>We want to walk the vcup structure to transform it into a DOM structure, and while doing that we also want to keep track of the reactive effects which are used for updating the text nodes.</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defn-</span> <span class="hljs-title">set-element-attribute</span> [<span class="hljs-comment">^js/Element</span> element attribute-name attribute-value]
  (<span class="hljs-name"><span class="hljs-builtin-name">cond</span></span>
    <span class="hljs-comment">;; Event listener</span>
    (<span class="hljs-name">str/starts-with?</span> attribute-name <span class="hljs-string">"on-"</span>)
    (<span class="hljs-name">.addEventListener</span> element
                       (<span class="hljs-name"><span class="hljs-builtin-name">-&gt;</span></span> attribute-name (<span class="hljs-name">subs</span> (<span class="hljs-name"><span class="hljs-builtin-name">count</span></span> <span class="hljs-string">"on-"</span>)))
                       attribute-value)

    <span class="hljs-symbol">:else</span>
    <span class="hljs-symbol">:to-be-defined-in-next-articles</span>))

(<span class="hljs-keyword">defn</span> <span class="hljs-title">process-vcup</span> [vcup]
  (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [all-effects (<span class="hljs-name"><span class="hljs-builtin-name">atom</span></span> [])
        to-dom-elements (<span class="hljs-name"><span class="hljs-builtin-name">fn</span></span> to-dom-elements [vcup]
                          (<span class="hljs-name"><span class="hljs-builtin-name">cond</span></span>
                            (<span class="hljs-name"><span class="hljs-builtin-name">string?</span></span> vcup)
                            [(<span class="hljs-name">js/document.createTextNode</span> vcup)]

                            (<span class="hljs-name"><span class="hljs-builtin-name">instance?</span></span> VcupNode vcup)
                            (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [<span class="hljs-comment">^js/Element</span> element (<span class="hljs-name">js/document.createElement</span> (<span class="hljs-name"><span class="hljs-builtin-name">name</span></span> (<span class="hljs-symbol">:element-tag</span> vcup)))
                                  [attributes children] (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [children (<span class="hljs-symbol">:children</span> vcup)
                                                              x (<span class="hljs-name"><span class="hljs-builtin-name">first</span></span> children)]
                                                          (<span class="hljs-name"><span class="hljs-builtin-name">if</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">and</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">map?</span></span> x)
                                                                   (<span class="hljs-name"><span class="hljs-builtin-name">not</span></span> (<span class="hljs-name">record?</span> x))) <span class="hljs-comment">; because map? returns true on records</span>
                                                            [x (<span class="hljs-name"><span class="hljs-builtin-name">next</span></span> children)]
                                                            [<span class="hljs-literal">nil</span> children]))
                                  <span class="hljs-comment">;; Convert the children into elements.</span>
                                  child-elements (<span class="hljs-name"><span class="hljs-builtin-name">into</span></span> []
                                                       (<span class="hljs-name"><span class="hljs-builtin-name">comp</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">remove</span></span> <span class="hljs-literal">nil</span>?)
                                                             <span class="hljs-comment">;; Inline elements when child is a seq.</span>
                                                             (<span class="hljs-name"><span class="hljs-builtin-name">mapcat</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">fn</span></span> [child]
                                                                      (<span class="hljs-name"><span class="hljs-builtin-name">if</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">seq?</span></span> child)
                                                                          child
                                                                          [child])))
                                                             (<span class="hljs-name"><span class="hljs-builtin-name">mapcat</span></span> to-dom-elements))
                                                       children)]

                              <span class="hljs-comment">;; Set the attributes on the created element.</span>
                              (<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [[attribute-name attribute-value] attributes]
                               (<span class="hljs-name">set-element-attribute</span> element (<span class="hljs-name"><span class="hljs-builtin-name">name</span></span> attribute-name) attribute-value))
                              <span class="hljs-comment">;; Set the element's children</span>
                              (<span class="hljs-name"><span class="hljs-builtin-name">doseq</span></span> [child-element child-elements]
                                (<span class="hljs-name"><span class="hljs-builtin-name">-&gt;</span></span> element (<span class="hljs-name">.appendChild</span> child-element)))
                              [element])

                            (<span class="hljs-name"><span class="hljs-builtin-name">instance?</span></span> sr/ReactiveNode vcup)
                            (<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [element (<span class="hljs-name">js/document.createTextNode</span> <span class="hljs-string">""</span>)
                                  effect (<span class="hljs-name">sr/create-effect</span> (<span class="hljs-name"><span class="hljs-builtin-name">fn</span></span> []
                                                             (<span class="hljs-name"><span class="hljs-builtin-name">set!</span></span> (<span class="hljs-name">.-textContent</span> element) @vcup)))]
                              (<span class="hljs-name"><span class="hljs-builtin-name">swap!</span></span> all-effects conj effect)
                              [element])

                            <span class="hljs-symbol">:else</span>
                            <span class="hljs-symbol">:to-be-defined-in-next-articles</span>))
        elements (<span class="hljs-name">to-dom-elements</span> vcup)]
    {<span class="hljs-symbol">:effects</span> @all-effects
     <span class="hljs-symbol">:elements</span> elements}))
</code></pre>
<p>And that’s it! We now have the basics of a reactive web framework + a working example with only <a target="_blank" href="https://github.com/metosin/vrac/blob/article/002-whats-in-a-component/blog-posts/002-whats-in-a-component/src/example/core.cljs">111 lines of code in total</a>, all thanks to <a target="_blank" href="https://github.com/metosin/signaali">Signaali</a>’s support.</p>
<p>In the next article I will make the range of the <code>for</code> loop dynamic — a change with a lot of technical implications. Stay tuned!</p>
<div class="hn-embed-widget" id="kofi"></div>]]></content:encoded></item><item><title><![CDATA[Making HTML reactive using Signaali]]></title><description><![CDATA[Signaali is a library which provides functions to build reactive systems. In this article, I describe how to use it to change the DOM when an event happens or when a data changes.
The complete source code displayed in this article can be found in Vra...]]></description><link>https://blog.404.taipei/making-html-reactive-using-signaali</link><guid isPermaLink="true">https://blog.404.taipei/making-html-reactive-using-signaali</guid><category><![CDATA[Vrac]]></category><category><![CDATA[Signaali]]></category><category><![CDATA[Clojure]]></category><category><![CDATA[ClojureScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Vincent Cantin (green-coder)]]></dc:creator><pubDate>Mon, 06 Jan 2025 15:22:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736190726401/0ebc894d-ea39-443f-99c2-86c342ff36d0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/metosin/signaali">Signaali</a> is a library which provides functions to build reactive systems. In this article, I describe how to use it to change the DOM when an event happens or when a data changes.</p>
<p>The complete source code displayed in this article can be found in <a target="_blank" href="https://github.com/metosin/vrac/tree/article/001-making-html-reactive-using-signaali/blog-posts/001-making-html-reactive-using-signaali">Vrac’s repository</a>.</p>
<h2 id="heading-lets-make-an-html-page">Let’s make an HTML page</h2>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width,initial-scale=1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Making HTML reactive using Signaali<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/js/main.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Reactive HTML without a framework<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This demonstrates how to use Signaali to manipulate the DOM reactively.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Reactive counter<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        Counter value: <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"counter-element"</span>&gt;</span>n/a<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"inc-counter-button"</span>&gt;</span>Increment<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"reset-counter-button"</span>&gt;</span>Reset<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Lazy effects<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        The effects are lazy, the user decides when to re-run them.
        Click the button to update the DOM.
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"run-effects-button"</span>&gt;</span>Run effects<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>This page has 3 buttons on which we will listen the “click” event. It also has a span element containing a text which we will update from a Signaali effect. Here is the skeleton of the source code. It has no reactivity yet, the callback functions are registered and unregistered, but they do nothing yet.</p>
<pre><code class="lang-clojure">(<span class="hljs-name"><span class="hljs-builtin-name">ns</span></span> example.core) <span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> require signaali's namespace</span>

<span class="hljs-comment">;; Get the references to the DOM elements we want to modify</span>
(<span class="hljs-keyword">def</span> <span class="hljs-comment">^js</span> <span class="hljs-title">counter-element</span>      (<span class="hljs-name">js/document.getElementById</span> <span class="hljs-string">"counter-element"</span>))
(<span class="hljs-keyword">def</span> <span class="hljs-comment">^js</span> <span class="hljs-title">inc-counter-button</span>   (<span class="hljs-name">js/document.getElementById</span> <span class="hljs-string">"inc-counter-button"</span>))
(<span class="hljs-keyword">def</span> <span class="hljs-comment">^js</span> <span class="hljs-title">reset-counter-button</span> (<span class="hljs-name">js/document.getElementById</span> <span class="hljs-string">"reset-counter-button"</span>))
(<span class="hljs-keyword">def</span> <span class="hljs-comment">^js</span> <span class="hljs-title">run-effects-button</span>   (<span class="hljs-name">js/document.getElementById</span> <span class="hljs-string">"run-effects-button"</span>))

<span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> a state for the counter</span>

<span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> an effect that updates the text in the DOM element `counter-element`</span>

(<span class="hljs-keyword">defn</span> <span class="hljs-title">on-inc-counter-button-clicked</span> []
  <span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> increase the counter state</span>
  ,)

(<span class="hljs-keyword">defn</span> <span class="hljs-title">on-reset-counter-button-clicked</span> []
  <span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> set the counter state to zero</span>
  ,)

(<span class="hljs-keyword">defn</span> <span class="hljs-title">on-run-effects-button-clicked</span> []
  <span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> run the effect which will update the DOM</span>
  ,)

(<span class="hljs-keyword">defn</span> <span class="hljs-title">setup!</span> []
  (<span class="hljs-name">.addEventListener</span> inc-counter-button   <span class="hljs-string">"click"</span> on-inc-counter-button-clicked)
  (<span class="hljs-name">.addEventListener</span> reset-counter-button <span class="hljs-string">"click"</span> on-reset-counter-button-clicked)
  (<span class="hljs-name">.addEventListener</span> run-effects-button   <span class="hljs-string">"click"</span> on-run-effects-button-clicked)

  <span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> set the counter's state to zero</span>
  <span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> run the effect once, to update the DOM</span>
  ,)

(<span class="hljs-keyword">defn</span> <span class="hljs-title">shutdown!</span> []
  (<span class="hljs-name">.removeEventListener</span> inc-counter-button   <span class="hljs-string">"click"</span> on-inc-counter-button-clicked)
  (<span class="hljs-name">.removeEventListener</span> reset-counter-button <span class="hljs-string">"click"</span> on-reset-counter-button-clicked)
  (<span class="hljs-name">.removeEventListener</span> run-effects-button   <span class="hljs-string">"click"</span> on-run-effects-button-clicked)

  <span class="hljs-comment">;; <span class="hljs-doctag">TODO:</span> dispose the effect, to avoid memory leaks.</span>
  ,)


<span class="hljs-comment">;; Shadow-CLJS hooks: start &amp; reload the app</span>

(<span class="hljs-keyword">defn</span> <span class="hljs-title">start-app</span> []
  (<span class="hljs-name">setup!</span>))

(<span class="hljs-keyword">defn</span> ^<span class="hljs-symbol">:dev/before-load</span> <span class="hljs-title">stop-app</span> []
  (<span class="hljs-name">shutdown!</span>))

(<span class="hljs-keyword">defn</span> ^<span class="hljs-symbol">:dev/after-load</span> <span class="hljs-title">restart-app</span> []
  (<span class="hljs-name">setup!</span>))
</code></pre>
<p>Let’s start using Signaali by including the namespace:</p>
<pre><code class="lang-clojure">(<span class="hljs-name"><span class="hljs-builtin-name">ns</span></span> example.core
  (<span class="hljs-symbol">:require</span> [signaali.reactive <span class="hljs-symbol">:as</span> sr]))
</code></pre>
<p>Then we need a state for the counter</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">def</span> <span class="hljs-title">counter-state</span>
  (<span class="hljs-name">sr/create-state</span> <span class="hljs-literal">nil</span>
                   <span class="hljs-comment">;; Optional param, useful for debugging</span>
                   {<span class="hljs-symbol">:metadata</span> {<span class="hljs-symbol">:name</span> <span class="hljs-string">"counter state"</span>}}))
</code></pre>
<p>Then we need an effect which will update the DOM node with a text version of the counter’s value.</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">def</span> <span class="hljs-title">counter-text-updater</span>
  (<span class="hljs-name">sr/create-effect</span> #(<span class="hljs-name"><span class="hljs-builtin-name">set!</span></span> (<span class="hljs-name">.-textContent</span> counter-element) (<span class="hljs-name"><span class="hljs-builtin-name">str</span></span> @counter-state))
                    <span class="hljs-comment">;; Optional param, useful for debugging</span>
                    {<span class="hljs-symbol">:metadata</span> {<span class="hljs-symbol">:name</span> <span class="hljs-string">"counter text updater"</span>}}))
</code></pre>
<p>Now let’s make the callbacks interact with Signaali’s state and effect.</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defn</span> <span class="hljs-title">on-inc-counter-button-clicked</span> []
  (<span class="hljs-name"><span class="hljs-builtin-name">swap!</span></span> counter-state inc))

(<span class="hljs-keyword">defn</span> <span class="hljs-title">on-reset-counter-button-clicked</span> []
  (<span class="hljs-name"><span class="hljs-builtin-name">reset!</span></span> counter-state <span class="hljs-number">0</span>))

(<span class="hljs-keyword">defn</span> <span class="hljs-title">on-run-effects-button-clicked</span> []
  (<span class="hljs-name">sr/re-run-stale-effectful-nodes</span>))
</code></pre>
<p>And last, we need to initialize the state and the DOM element’s text on setup, and dispose the effect on shutdown.</p>
<pre><code class="lang-clojure">(<span class="hljs-keyword">defn</span> <span class="hljs-title">setup!</span> []
  (<span class="hljs-name">.addEventListener</span> inc-counter-button   <span class="hljs-string">"click"</span> on-inc-counter-button-clicked)
  (<span class="hljs-name">.addEventListener</span> reset-counter-button <span class="hljs-string">"click"</span> on-reset-counter-button-clicked)
  (<span class="hljs-name">.addEventListener</span> run-effects-button   <span class="hljs-string">"click"</span> on-run-effects-button-clicked)

  (<span class="hljs-name"><span class="hljs-builtin-name">reset!</span></span> counter-state <span class="hljs-number">0</span>)
  (<span class="hljs-name">sr/add-on-dispose-callback</span> counter-text-updater #(<span class="hljs-name"><span class="hljs-builtin-name">set!</span></span> (<span class="hljs-name">.-textContent</span> counter-element) <span class="hljs-string">"n/a"</span>))
  @counter-text-updater)

(<span class="hljs-keyword">defn</span> <span class="hljs-title">shutdown!</span> []
  (<span class="hljs-name">sr/dispose</span> counter-text-updater)

  (<span class="hljs-name">.removeEventListener</span> inc-counter-button   <span class="hljs-string">"click"</span> on-inc-counter-button-clicked)
  (<span class="hljs-name">.removeEventListener</span> reset-counter-button <span class="hljs-string">"click"</span> on-reset-counter-button-clicked)
  (<span class="hljs-name">.removeEventListener</span> run-effects-button   <span class="hljs-string">"click"</span> on-run-effects-button-clicked))
</code></pre>
<p>You now know most of what you need to start your own web framework in Clojure(script).</p>
<p>In the next blog post of this series, I will start writing helpers to avoid most of the boilerplate we had to type above. That’s usually how web frameworks are born.</p>
<div class="hn-embed-widget" id="kofi"></div>]]></content:encoded></item></channel></rss>