CHANGELOG | API | current Break Version:
[com.taoensso/tengen "1.1.0"] ; Mature/stable (basically "done")
See here if to help support my open-source work, thanks! - Peter Taoussanis
Tengen: let-based Reagent components for Clojure/Script
Ten-gen (天元) is a Japanese Go term for the central, and only unique point on the Go board.
Reactjs has its pros and cons. Overall, it can be a good fit for web/native application development with Clojure/Script.
But while React's lifecycle methods are flexible, using them correctly can be a little unintuitive.
One of the sharper edges I've found in practice, is the difficulty of managing simple state flow through the component lifecycle process. This can actually be harder to do in Clojure/Script than vanilla JS since Clojure intentionally discourages the kind of disposable mutable state that could be handy here.
Net result: one sees a lot of weird contortions using atoms and core.async channels just to get the basic kind of data flow that you'll routinely need in a real application.
As an alternative: Tengen gives you a small, simple, lightweight component constructor that uses the unique capabilities of Lisp macros to get this:
(def-cmptfn my-example-component
"Optional docstring"
[first-name last-name] ; Args given to component (will rerender on changes)
:let-mount ; Optional bindings established on each mount, available downstream
[norm-fn (fn [s] (str/upper-case (str s)))
_ (do ) ; Any side-effects on mount (fetch data from server, etc.)
]
:let-render ; Optional bindings established on each render, available downstream
[norm-first-name (norm-fm first-name)
norm-last-name (norm-fn last-name)
;;; We also have access to two magic symbols:
currently-mounting? this-mounting? ; Magic `this-mounting?` binding
current-cmpt this-cmpt ; Magic `this-cmpt` binding
]
:render ; Have all above bindings
[:div "Full name is: "
(str norm-first-name " " norm-last-name)]
:post-render (do) ; Optional: modify state atoms, etc. Have all above bindings.
:unmount (do) ; Optional: any cleanup jobs, etc. Have all above bindings.
)
That is:
:let-mount
and:let-render
bindings automatically flow down through all later lifecycle stages.- Magic
this-mounting?
andthis-cmpt
bindings are automatically available through all lifecycle stages.
These two small features can help cut out a lot of unnecessary complexity when writing real applications. In particular, you'll almost never need to touch or even be aware of the underlying React lifecycle methods.
Add the necessary dependency to your project:
Leiningen: [com.taoensso/tengen "1.1.0"] ; or
deps.edn: com.taoensso/tengen {:mvn/version "1.1.0"}
And setup your namespace imports:
(ns my-cljs-ns
(:require [taoensso.tengen.reagent :as tengen :refer-macros [cmptfn def-cmptfn]]))
And you're good to go, you've already seen the entire API!
Check the cmptfn
, def-cmptfn
docstrings for more info.
Just the most familiar with Reagent, so started there. Haven't had time yet to look at extending to other libs, but should be trivial if there's demand (please ping to let me know).
I'll note that Rum's design in particular looks quite pleasant.
Tengen doesn't add any detectable overhead to your components, it's just a lightweight macro wrapper to Reagent's usual constructor.
It doesn't, you can continue to use whatever higher-level state management strategies you prefer.
As usual for Reagent, use ref callbacks:
(def-cmptfn my-example-component [arg1 arg2]
[:div
{:ref
(fn [node]
(when node
;; node is mounted in DOM
))}])
You can also use (reagent.core/dom-node this-cmpt)
, etc. - but would strongly recommend preferring ref callbacks in general since they're a lot more reliable and React's findDOMNode
method is expected to become deprecated soon.
Please use the project's GitHub issues page for all questions, ideas, etc. Pull requests welcome. See the project's GitHub contributors page for a list of contributors.
Otherwise, you can reach me at Taoensso.com. Happy hacking!
Distributed under the EPL v1.0 (same as Clojure).
Copyright © 2016-2022 Peter Taoussanis.