DEV Community

Kasey Speakman
Kasey Speakman

Posted on

ClojureScript simple MVU loop refined

In dynamic languages, it turns out the MVU init function is unnecessary.

In the previous article I developed a simple MVU loop. As one of the TODO items, I posited that it would be possible to eliminate the init function and turn it into an event. Well, I did that. I also removed history since it is currently unused and unexposed functionality. And it shrunk both the mvu code and the app code. The MVU code is down to about 35 narrow lines of code.

Update: I created an async version.

Here is the app code.

(ns core
  (:require [mvu]))

(defn updatef [model [tag data :as event]]
  (case tag
    :init [{:count 0} []]
    :clicked [(update model :count inc) []]
    [model []]))

(defn render [{count :count :as model}]
   [:button {:type "button" :on-click #(mvu/notify :clicked)} "Click me"]
   [:div "Count " count]])

(defn perform [model effect]

(def config {:mvu/update updatef
             :mvu/render render
             :mvu/render-node (js/document.getElementById "app")
             :mvu/perform perform})

(defn ^:dev/after-load on-reload []
  (mvu/on-reload config))

(defn main [& args]
  (mvu/create config))

Now "init" is just another event handled by the update function.

And here is the MVU code now.

(ns mvu
  (:require [reagent.dom :as rdom]))

(defonce state-atom (atom {}))

(defn default-log [event next-model effects]
  (js/console.log (clj->js
                   {:event (filter identity event)
                    :model next-model
                    :effects effects})))

(def defaults {::log default-log
               ::init-event [:init]
               ::model {}})

(defn notify [& event]
  (let [{updatef ::update
         render ::render
         render-node ::render-node
         perform ::perform
         log ::log
         model ::model} @state-atom
        [model effects] (updatef model event)]
    (cond goog.DEBUG (log event model effects))
    (swap! state-atom assoc ::model model)
    (rdom/render (render model) render-node)
    (doseq [effect effects]
      (perform model effect))))

(defn on-reload [config]
  (let [{render ::render
         render-node ::render-node
         log ::log
         model ::model
         :as state} (merge @state-atom config)]
    (cond goog.DEBUG (log [:hot-reloaded] model []))
    (reset! state-atom state)
    (rdom/render (render model) render-node)))

(defn create [config]
  (let [state (merge defaults config)]
    (reset! state-atom state)
    (apply notify (::init-event state))))

Notice that MVU provides some fairly obvious defaults. The model is an empty map and the init event is just :init. You can override this by providing a different init event in the start config. You can also do this for the initial model and the log function.

Clojure + MVU is such a great combo.

Top comments (0)