Fold your state into the DOM!
A minimal zero-deps Reagent-like in Squint and CLJS.
Quickstart example:
(ns my-app
(:require ["https://esm.sh/reagami" :as reagami]))
(def state (atom {:counter 0}))
(defn my-component []
[:div
[:div "Counted: " (:counter @state)]
[:button {:on-click #(swap! state update :counter inc)}
"Click me!"]])
(defn render []
(reagami/render (js/document.querySelector "#app") [my-component]))
(add-watch state ::render (fn [_ _ _ _]
(render)))
(render)(Open this example on the Squint playground)
In ClojureScript you would add this library to your deps.edn :deps as follows:
io.github.borkdude/reagami {:git/sha "<latest-sha>" :git/tag "<latest-tag>"}and then require it with (:require [reagami.core :as reagami]).
Reagami supports:
- Building small reactive apps with the only dependency being Squint or CLJS. Smallest app with Squint after minification is around 5kb gzip.
- Rendering hiccup into a container DOM node. The only public function is
render. - Event handlers via
:on-click,:on-input, etc. - Default attributes:
:default-value, etc. for uncontrolled components - Id and class short notation:
[:div#foo.class1.class2] - Disabling properties with
false:[:button {:disabled (not true)}] :stylemaps:{:style {:background-color :green}}:on-renderhook. See docs here.
Reagami does NOT support:
- Auto-rerendering by auto-watching custom atoms. Instead you use
add-watch+renderon regular atoms or you callrenderyourself. - React hooks (it doesn't use React)
Local state can be accomplished by using nested renders like in this example or using web components.
Reagami uses a basic patching algorithm explained in this blog post. It may become more advanced in the future, but the (fun) point of this library at this point is that it's small, underengineered and thus suited for educational purposes.
For a more fully featured version of Reagent in squint, check out Eucalypt.
The :on-render hook can be used to do something after a DOM node is mounted, updated or unmounted.
It takes 3 arguments: (fn [node lifecycle data])
node: the DOM node that is mounted, updated or unmounted.lifecycle: one of:mount,:updateor:unmountdata: the result of the:on-renderfunction every time it is called. By returning data you can pass data from one lifecycle to another. E.g. when you mount a JS component, you can return{:unmount unmount}so you can call the unmount function in the:unmountlifecycle.
Example:
(fn [node lifecycle {:keys [unmount updates] :as data}]
(case lifecycle
:mount
{:unmount (install-clock! node)
:updates 0}
:update
(update data :updates inc)
:unmount
(do
(println "Number of updates in total: " updates)
(unmount))))See a full working example on the playground.
Examples on the Squint playground:
- Input field + counter
- Boring crud table
- Snake game
- Draggable button
- CSS transition
- Ohm's law
- Multi select
- Web component
MIT