anmonteiro Code ramblings

Om Next meets Devcards — the full reloadable experience

In a recent post, I've put together a checklist covering the steps involved in writing reloadable Om Next code. If you are trying to use them in your devcards, however, you might find that current Devcards helpers for Om Next are still lacking full support for a pleasant, out-of-the-box reloadable experience, even when sticking to every recommendation in that list. Enter `devcards-om-next`.

The state of Om Next helpers for Devcards

As mentioned in the checklist, Om Next's own add-root! will unmount any components currently mounted on the target DOM node before actually mounting the component in question. In a reloadable scenario, this behavior is undesirable because it will result in components losing their local state. Unfortunately, the current Devcards om-next-root and defcard-om-next helpers employ this exact approach, which although simple, makes impossible to set up an actual interactive programming environment.

The full experience: devcards-om-next

devcards-om-next is a small Devcards extension that aims to replace the current helpers with ones that know the dynamics of Om Next components. By being aware of how exactly to mount and reload components, these new om-next-root and defcard-om-next helpers enable the creation of fully reloadable Om Next component cards. For now, they are in their own library, but are expected to be integrated in Devcards itself going forward. Read on for an example of how to use them in your code. Also refer to the devcards demos in the repository itself for more information.

Examples / How-tos

  • Start by adding the devcards-om-next dependency information to your project:
;; for Leiningen:
[devcards-om-next "0.3.0"]
  • Require the devcards-om-next namespace, as well as the macros you intend to use:
(ns my-ns.core
  (:require [devcards-om-next.core :as don
             :refer-macros [om-next-root defcard-om-next]]
            [om.next :as om :refer-macros [defui]]))
  • Write your cards in the normal reloadable manner. om-next-root is the simplest of the two. defcard-om-next is a shortcut for defcard plus om-next-root. Both take an Om Next component and a reconciler, but they can also take a state map or atom instead. Below is a small example. I encourage you to run it with Figwheel, increment the counter, modify e.g. the button label and watch your changes being pushed to the browser as the component's local state remains unchanged 1. Pretty cool!
;; use ^:once meta in `defui`
(defui ^:once Counter
  Object
  (initLocalState [this]
    {:val 1})
  (render [this]
    (let [{:keys [val]} (om/get-state this)]
      (dom/div nil
        (str "val: " val)
        (dom/button
          #js {:onClick #(om/update-state! this update :val inc)}
          "inc!")))))

;; defonce the reconciler
(defonce counter-reconciler
  (om/reconciler {:state {}
                  :parser {:read (fn [] {:value {}})}}))

;; the usual `defcard` calls `om-next-root`
(defcard om-next-root-example
  "`om-next-root` takes a component class and (optionally)
   a map with the state or a reconciler"
  (om-next-root Counter))

;; `defcard-om-next` takes every normal `defcard` argument
;; (documentation, devcard, options, etc.), and the arguments of `om-next-root`
(defcard-om-next defcard-om-next-example
  "`defcard-om-next` example with a Component class and a reconciler"
  Counter
  counter-reconciler)

I hope these new helpers are useful in your journey writing reloadable Om Next cards. Thanks for reading!


1 keep in mind that you must still follow the recommendations in the reloadable code checklist to see this in action i.e., you must supply a reconciler that has been defined with defonce for it to work. The (om-next-root Component) or (defcard-om-next my-card Component) shortcuts don't count as reloadable (they define a reconciler under the hood everytime) and will therefore reset the component's local state.